From 1236c0a7a9cfec3925b06156063d5715d22720c7 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Sun, 18 Jan 2026 20:33:42 +0800 Subject: [PATCH 01/12] SCRIPTS-#644 rewrote addNewDancers.js and changeDancerColor.js. also added some tests. --- scripts/editing/addNewDancers.js | 218 +++++++++++----------- scripts/editing/addNewDancers.test.js | 45 +++++ scripts/editing/changeDancerColor.js | 132 ++++++++++--- scripts/editing/changeDancerColor.test.js | 204 ++++++++++++++++++++ 4 files changed, 459 insertions(+), 140 deletions(-) create mode 100644 scripts/editing/addNewDancers.test.js create mode 100644 scripts/editing/changeDancerColor.test.js diff --git a/scripts/editing/addNewDancers.js b/scripts/editing/addNewDancers.js index 1b012a71c..692377f36 100644 --- a/scripts/editing/addNewDancers.js +++ b/scripts/editing/addNewDancers.js @@ -1,126 +1,116 @@ -const oldData = require("./out/exportData.json"); -const newData = require("../../utils/jsons/exportDataEmpty.json"); - -const generateDefaultLocation = (dancerData) => { - const length = dancerData.length; - const spacing = 1; - const pos = dancerData.map((val, index) => [ - -5, - (index - (length - 1) / 2) * spacing, - 0, - ]); - return pos; +/* + * This script merges old dancer data with new dancer data, ensuring that new dancers + * are properly initialized with default positions, rotations, and effects. + * + * To test out this script, simply run + * `node scripts/editing/addNewDancers.test.js` + * + * The merged data will be saved to `out/exportData_merged.json`. + */ + +const fs = require("fs"); +const path = require("path"); + +const DEFAULT_POSITION_X = -5; +const DEFAULT_SPACING = 1; +const DEFAULT_ROTATION = [0, 0, 0]; +const DEFAULT_COLOR = ["black", 0]; + +const generateDefaultLocation = (dancers) => { + const length = dancers.length; + return dancers.map((_, index) => [ + DEFAULT_POSITION_X, + (index - (length - 1) / 2) * DEFAULT_SPACING, + 0, + ]); }; -const merge = ( - oldData, - newData, - defaultLocation, - defaultRotation, - defaultColorData, - defaultEffectData -) => { - let mergedDancer = newData.dancer; - let mergedPosition = oldData.position; - let mergedControl = oldData.control; - let mergedColor = oldData.color; - let mergedLEDEffects = oldData.LEDEffects; - - // Create default position for new dancers - Object.keys(mergedPosition).forEach((id) => { - let positionFrame = oldData.position[id]; - let loc = positionFrame.location; - for (let i = loc.length; i < mergedDancer.length; i++) { - loc.push(defaultLocation[i]); - } - let rot = positionFrame.rotation; - for (let i = rot.length; i < mergedDancer.length; i++) { - rot.push(defaultRotation); - } - }); - - // Create default status for new dancers - Object.keys(mergedControl).forEach((id) => { - let controlFrame = oldData.control[id]; - let status = controlFrame.status; - let ledStatus = controlFrame.led_status; - for (let i = status.length; i < mergedDancer.length; i++) { - const dancerParts = mergedDancer[i].parts; - let newStatus = []; - dancerParts.forEach((part) => { - if (part.type == "FIBER") { - newStatus.push(defaultColorData); - } else { - newStatus.push(defaultEffectData); - } - }); - - status.push(newStatus); - } - for (let i = ledStatus.length; i < mergedDancer.length; i++) { - const dancerParts = mergedDancer[i].parts; - let newLedStatus = []; - dancerParts.forEach((part) => { - if (part.type == "FIBER") { - newLedStatus.push([]); - } else { - newLedStatus.push([]); - } - }); - ledStatus.push(newLedStatus); +const extendArray = (arr, targetLength, fillFn) => { + for (let i = arr.length; i < targetLength; i++) { + arr.push(fillFn(i)); } - }); +}; - // Create default effects for new dancers - newData.dancer.forEach((dancer) => { - if (dancer.model in mergedLEDEffects) { - return; - } +const merge = (oldData, newData, defaultLocation) => { + const mergedDancer = newData.dancer; + const mergedPosition = oldData.position; + const mergedControl = oldData.control; + const mergedColor = oldData.color; + const mergedLEDEffects = oldData.LEDEffects; + + // Extend positions for new dancers + Object.values(mergedPosition).forEach((frame) => { + extendArray(frame.location, mergedDancer.length, (i) => defaultLocation[i]); + extendArray(frame.rotation, mergedDancer.length, () => DEFAULT_ROTATION); + }); - let dancerEffects = {}; - dancer.parts.forEach((part) => { - let partEffects = {}; - if (part.type == "LED") { - Object.keys(mergedColor).forEach((colorName) => { - partEffects[colorName] = { - repeat: 0, - frames: [ - { - LEDs: Array(part.length).fill([colorName, 255]), - start: 0, - fade: false, - }, - ], - }; + // Extend control status for new dancers + Object.values(mergedControl).forEach((frame) => { + extendArray(frame.status, mergedDancer.length, (i) => { + return mergedDancer[i].parts.map(() => DEFAULT_COLOR); }); - dancerEffects[part.name] = partEffects; - } + extendArray(frame.led_status, mergedDancer.length, (i) => { + return mergedDancer[i].parts.map(() => []); + }); }); - mergedLEDEffects[dancer.model] = dancerEffects; - }); + // Initialize LED effects for new dancers + mergedDancer.forEach((dancer) => { + if (dancer.model in mergedLEDEffects) return; + + const dancerEffects = {}; - return { - dancer: mergedDancer, - position: mergedPosition, - control: mergedControl, - color: mergedColor, - LEDEffects: mergedLEDEffects, - }; + dancer.parts.forEach((part) => { + if (part.type !== "LED") return; + + const partEffects = {}; + + Object.keys(mergedColor).forEach((colorName) => { + partEffects[colorName] = { + repeat: 0, + frames: [ + { + LEDs: Array(part.length).fill([colorName, 255]), + start: 0, + fade: false, + }, + ], + }; + }); + + dancerEffects[part.name] = partEffects; + }); + + mergedLEDEffects[dancer.model] = dancerEffects; + }); + + return { + dancer: mergedDancer, + position: mergedPosition, + control: mergedControl, + color: mergedColor, + LEDEffects: mergedLEDEffects, + }; }; -const defaultLocation = generateDefaultLocation(newData.dancer); -const defaultRotation = [0, 0, 0]; -const defaultColorData = ["black", 0]; -const defaultEffectData = ["black", 0]; -const mergedData = merge( - oldData, - newData, - defaultLocation, - defaultRotation, - defaultColorData, - defaultEffectData -); - -console.log(JSON.stringify(mergedData, null, 2)); +// Main execution +if (require.main === module) { + try { + const oldData = JSON.parse(fs.readFileSync("./out/exportData.json", "utf-8")); + const newData = JSON.parse(fs.readFileSync("../../utils/jsons/exportDataEmpty.json", "utf-8")); + + const defaultLocation = generateDefaultLocation(newData.dancer); + const mergedData = merge(oldData, newData, defaultLocation); + + const outputPath = path.join("./out", "exportData_merged.json"); + fs.writeFileSync(outputPath, JSON.stringify(mergedData, null, 2)); + console.log(JSON.stringify(mergedData, null, 2)); + console.log(`✓ Merged data written to ${outputPath}`); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); + } +} + +module.exports = { merge, generateDefaultLocation, extendArray }; \ No newline at end of file diff --git a/scripts/editing/addNewDancers.test.js b/scripts/editing/addNewDancers.test.js new file mode 100644 index 000000000..30397c016 --- /dev/null +++ b/scripts/editing/addNewDancers.test.js @@ -0,0 +1,45 @@ +const { merge, generateDefaultLocation } = require("./addNewDancers"); + +// Mock data for testing +const mockOldData = { + dancer: [{ model: "dancer1", parts: [] }], + position: { + frame_0: { + location: [[0, 0, 0]], + rotation: [[0, 0, 0]], + }, + }, + control: { + frame_0: { + status: [[["black", 0]]], + led_status: [[]], + }, + }, + color: { red: [255, 0, 0] }, + LEDEffects: {}, +}; + +const mockNewData = { + dancer: [ + { model: "dancer1", parts: [{ type: "FIBER", name: "head" }] }, + { model: "dancer2", parts: [{ type: "LED", name: "arm", length: 10 }] }, + ], + position: { frame_0: { location: [], rotation: [] } }, + control: { frame_0: { status: [], led_status: [] } }, + color: { red: [255, 0, 0], blue: [0, 0, 255] }, + LEDEffects: {}, +}; + +// Test 1: generateDefaultLocation +console.log("Test 1: generateDefaultLocation"); +const defaultLoc = generateDefaultLocation(mockNewData.dancer); +console.log("✓ Generated locations:", defaultLoc.length === 2 ? "PASS" : "FAIL"); + +// Test 2: merge function +console.log("\nTest 2: merge function"); +const result = merge(mockOldData, mockNewData, defaultLoc); +console.log("✓ Dancers count:", result.dancer.length === 2 ? "PASS" : "FAIL"); +console.log("✓ Position extended:", result.position.frame_0.location.length === 2 ? "PASS" : "FAIL"); +console.log("✓ LED effects created:", result.LEDEffects.dancer2 ? "PASS" : "FAIL"); + +console.log("\nAll tests completed!"); \ No newline at end of file diff --git a/scripts/editing/changeDancerColor.js b/scripts/editing/changeDancerColor.js index 76c368811..37da47c7d 100644 --- a/scripts/editing/changeDancerColor.js +++ b/scripts/editing/changeDancerColor.js @@ -1,37 +1,117 @@ +/* + * This script updates dancer colors across all frames by replacing old color names + * with new ones based on dancer groups and part indices. + * + * To run this script, use the command: + * `node scripts/editing/changeDancerColor.test.js` + * + * The updated data will be saved to `scripts/editing/updatedColor.json`. + */ - - -const data = require("../../../LightTableBackup/2025.03.19.json"); const fs = require('fs'); const path = require('path'); -for (const [key, controlData] of Object.entries(data.control)) { - for (let part_id = 0; part_id < 31; part_id++) { - if (controlData.status[0][part_id][0] === "bad_blue" || controlData.status[0][part_id][0] === "bad_green" || controlData.status[0][part_id][0] === "bad_orange_light_p" || controlData.status[0][part_id][0] === "orange_dark_p") { - data.control[key].status[0][part_id][0] = "bad_orange_crayola"; - } - } - for (let dancer_id = 1; dancer_id < 5; dancer_id++) { - for (let part_id = 0; part_id < 39; part_id++) { - if (controlData.status[dancer_id][part_id][0] === "bad_blue" || controlData.status[dancer_id][part_id][0] === "bad_green" || controlData.status[dancer_id][part_id][0] === "bad_orange_light_p" || controlData.status[dancer_id][part_id][0] === "orange_dark_p") { - data.control[key].status[dancer_id][part_id][0] = "bad_orange_crayola"; - } - if (controlData.status[dancer_id][part_id][0] === "bad_blur_blue") { - data.control[key].status[dancer_id][part_id][0] = "bad_red"; - } - if (controlData.status[dancer_id][35][0] === "bad_orange_crayola") { - data.control[key].status[dancer_id][35][0] = "bad_red"; +const COLOR_MAPPINGS = [ + { + name: "Dancer 0 color fix", + dancerRange: [0, 1], + partRange: [0, 31], + colorMap: { + "bad_blue": "bad_orange_crayola", + "bad_green": "bad_orange_crayola", + "bad_orange_light_p": "bad_orange_crayola", + "orange_dark_p": "bad_orange_crayola", + }, + }, + { + name: "Dancer 1-4 color fix", + dancerRange: [1, 5], + partRange: [0, 39], + colorMap: { + "bad_blue": "bad_orange_crayola", + "bad_green": "bad_orange_crayola", + "bad_orange_light_p": "bad_orange_crayola", + "orange_dark_p": "bad_orange_crayola", + "bad_blur_blue": "bad_red", + }, + specialRules: [ + { + condition: (dancer_id, part_id) => part_id === 35, + originalColor: "bad_orange_crayola", + newColor: "bad_red", + }, + ], + }, + { + name: "Dancer 5-7 color fix", + dancerRange: [5, 8], + partRange: [0, 36], + colorMap: { + "pink": "good_Purple", + }, + }, +]; + +const applyColorMappings = (data) => { + for (const [frameKey, controlData] of Object.entries(data.control)) { + // console.log(`Processing frame key ${frameKey}...`); + for (const mapping of COLOR_MAPPINGS) { + const [dancerStart, dancerEnd] = mapping.dancerRange; + const [partStart, partEnd] = mapping.partRange; + + for (let dancer_id = dancerStart; dancer_id < dancerEnd; dancer_id++) { + if (!controlData.status[dancer_id]) continue; + + for (let part_id = partStart; part_id < partEnd; part_id++) { + if (!controlData.status[dancer_id][part_id]) continue; + + const currentColor = controlData.status[dancer_id][part_id][0]; + + // Check special rules FIRST (before regular mappings) + let colorChanged = false; + if (mapping.specialRules) { + for (const rule of mapping.specialRules) { + if ( + rule.condition(dancer_id, part_id) && + currentColor === rule.originalColor + ) { + controlData.status[dancer_id][part_id][0] = rule.newColor; + colorChanged = true; + break; + } + } + } + + // Apply regular color mappings only if no special rule was applied + if (!colorChanged && currentColor in mapping.colorMap) { + controlData.status[dancer_id][part_id][0] = mapping.colorMap[currentColor]; + } + } } } } - for (let dancer_id = 5; dancer_id < 8; dancer_id++) { - for (let part_id = 0; part_id < 36; part_id++) { - if (controlData.status[dancer_id][part_id][0] === "pink") { - data.control[key].status[dancer_id][part_id][0] = "good_Purple"; - } +}; + +// Main execution +if (require.main === module) { + try { + const inputPath = path.join(__dirname, "../../../LightTableBackup/2025.03.19.json"); + + if (!fs.existsSync(inputPath)) { + throw new Error(`Input file not found: ${inputPath}`); } + + const data = JSON.parse(fs.readFileSync(inputPath, "utf-8")); + + applyColorMappings(data); + + const outputPath = path.join(__dirname, "./updatedColor.json"); + fs.writeFileSync(outputPath, JSON.stringify(data, null, 2)); + console.log(`✓ Updated data saved to ${outputPath}`); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); } } -fs.writeFileSync(path.join(__dirname, "./updatedColor.json"), JSON.stringify(data, null, 2)); -console.log("Updated data has been saved to ./updatedColor.json"); \ No newline at end of file +module.exports = { applyColorMappings }; \ No newline at end of file diff --git a/scripts/editing/changeDancerColor.test.js b/scripts/editing/changeDancerColor.test.js new file mode 100644 index 000000000..e4e74432b --- /dev/null +++ b/scripts/editing/changeDancerColor.test.js @@ -0,0 +1,204 @@ +const { applyColorMappings } = require("./changeDancerColor"); + +// Mock data for testing +const createMockData = () => ({ + control: { + frame_0: { + status: [ + // Index 0 = Dancer 0 (parts 0-30, range is [0, 31]) + [ + ["bad_blue", 255], + ["bad_green", 255], + ["bad_orange_light_p", 255], + ["orange_dark_p", 255], + ["good_color", 255], + ...Array(26).fill(["unchanged", 255]), + ], + // Index 1 = Dancer 1 (parts 0-38, range is [0, 39]) + [ + ["bad_blue", 255], + ["bad_blur_blue", 255], + ["good_color", 255], + ...Array(32).fill(["unchanged", 255]), + ["bad_orange_crayola", 255], // part 35 - should become bad_red + ...Array(3).fill(["unchanged", 255]), + ], + // Index 2 = Dancer 2 (parts 0-38, range is [0, 39]) + [ + ["bad_green", 255], + ["unchanged", 255], + ...Array(33).fill(["unchanged", 255]), + ["bad_orange_crayola", 255], // part 35 - should become bad_red + ...Array(3).fill(["unchanged", 255]), + ], + // Index 3 = Dancer 3 (parts 0-38, range is [0, 39]) + [ + ["unchanged", 255], + ...Array(34).fill(["unchanged", 255]), + ["bad_orange_crayola", 255], // part 35 - should become bad_red + ...Array(3).fill(["unchanged", 255]), + ], + // Index 4 = Dancer 4 (parts 0-38, range is [0, 39]) + [ + ["unchanged", 255], + ...Array(34).fill(["unchanged", 255]), + ["bad_orange_crayola", 255], // part 35 - should become bad_red + ...Array(3).fill(["unchanged", 255]), + ], + // Index 5 = Dancer 5 (parts 0-35, range is [0, 36]) + [ + ["pink", 255], + ["good_Purple", 255], + ...Array(34).fill(["unchanged", 255]), + ], + ], + }, + frame_1: { + status: [ + // Dancer 0 + [ + ["bad_blue", 255], + ...Array(29).fill(["unchanged", 255]), + ], + // Dancer 1 + [ + ["bad_blur_blue", 255], + ...Array(34).fill(["unchanged", 255]), + ["bad_orange_crayola", 255], // part 35 + ...Array(3).fill(["unchanged", 255]), + ], + // Dancer 2 + [ + ["unchanged", 255], + ...Array(38).fill(["unchanged", 255]), + ], + // Dancer 3 + [ + ["unchanged", 255], + ...Array(38).fill(["unchanged", 255]), + ], + // Dancer 4 + [ + ["unchanged", 255], + ...Array(38).fill(["unchanged", 255]), + ], + // Dancer 5 + [ + ["pink", 255], + ...Array(35).fill(["unchanged", 255]), + ], + ], + }, + }, +}); + +// Test utilities +const assert = (condition, message) => { + if (!condition) { + console.error(`✗ FAIL: ${message}`); + process.exit(1); + } + console.log(`✓ PASS: ${message}`); +}; + +// Test 1: Dancer 0 color fixes +console.log("\n=== Test 1: Dancer 0 color fixes ==="); +const testData1 = createMockData(); +applyColorMappings(testData1); + +assert( + testData1.control.frame_0.status[0][0][0] === "bad_orange_crayola", + "Dancer 0, part 0: bad_blue → bad_orange_crayola" +); +assert( + testData1.control.frame_0.status[0][1][0] === "bad_orange_crayola", + "Dancer 0, part 1: bad_green → bad_orange_crayola" +); +assert( + testData1.control.frame_0.status[0][2][0] === "bad_orange_crayola", + "Dancer 0, part 2: bad_orange_light_p → bad_orange_crayola" +); +assert( + testData1.control.frame_0.status[0][3][0] === "bad_orange_crayola", + "Dancer 0, part 3: orange_dark_p → bad_orange_crayola" +); +assert( + testData1.control.frame_0.status[0][4][0] === "good_color", + "Dancer 0, part 4: unchanged colors stay the same" +); + +// Test 2: Dancer 1-4 color fixes with special rule +console.log("\n=== Test 2: Dancer 1-4 color fixes ==="); +const testData2 = createMockData(); +applyColorMappings(testData2); + +assert( + testData2.control.frame_0.status[1][0][0] === "bad_orange_crayola", + "Dancer 1, part 0: bad_blue → bad_orange_crayola" +); +assert( + testData2.control.frame_0.status[1][1][0] === "bad_red", + "Dancer 1, part 1: bad_blur_blue → bad_red" +); +assert( + testData2.control.frame_0.status[1][35][0] === "bad_red", + "Dancer 1, part 35 (special rule): bad_orange_crayola → bad_red" +); +assert( + testData2.control.frame_0.status[2][35][0] === "bad_red", + "Dancer 2, part 35 (special rule): bad_orange_crayola → bad_red" +); +assert( + testData2.control.frame_0.status[3][35][0] === "bad_red", + "Dancer 3, part 35 (special rule): bad_orange_crayola → bad_red" +); +assert( + testData2.control.frame_0.status[4][35][0] === "bad_red", + "Dancer 4, part 35 (special rule): bad_orange_crayola → bad_red" +); + +// Test 3: Dancer 5-7 color fixes +console.log("\n=== Test 3: Dancer 5-7 color fixes ==="); +const testData3 = createMockData(); +applyColorMappings(testData3); + +assert( + testData3.control.frame_0.status[5][0][0] === "good_Purple", + "Dancer 5, part 0: pink → good_Purple" +); + +// Test 4: Multiple frames processing +console.log("\n=== Test 4: Multiple frames processing ==="); +const testData4 = createMockData(); +applyColorMappings(testData4); + +assert( + testData4.control.frame_1.status[0][0][0] === "bad_orange_crayola", + "Frame 1, Dancer 0, part 0: bad_blue → bad_orange_crayola" +); +assert( + testData4.control.frame_1.status[1][0][0] === "bad_red", + "Frame 1, Dancer 1, part 0: bad_blur_blue → bad_red" +); +assert( + testData4.control.frame_1.status[1][35][0] === "bad_red", + "Frame 1, Dancer 1, part 35 (special rule): bad_orange_crayola → bad_red" +); + +// Test 5: Unchanged data remains unchanged +console.log("\n=== Test 5: Unchanged data remains unchanged ==="); +const testData5 = createMockData(); +const beforeAlpha = testData5.control.frame_0.status[0][4][1]; +applyColorMappings(testData5); +const afterAlpha = testData5.control.frame_0.status[0][4][1]; + +assert( + beforeAlpha === afterAlpha, + "Alpha values should not be modified" +); +assert( + testData5.control.frame_0.status[0][4][0] === "good_color", + "Unmapped colors should remain unchanged" +); + +console.log("\n=== All tests passed! ===\n"); \ No newline at end of file From f7ecbed964a0ceaac812dc0b77a273ccaa0c88f1 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Sun, 18 Jan 2026 21:24:02 +0800 Subject: [PATCH 02/12] rewrote changeLEDAlpha.js and add tests --- scripts/editing/changeLEDAlpha.js | 71 ++++++++-- scripts/editing/changeLEDAlpha.test.js | 181 +++++++++++++++++++++++++ 2 files changed, 237 insertions(+), 15 deletions(-) create mode 100644 scripts/editing/changeLEDAlpha.test.js diff --git a/scripts/editing/changeLEDAlpha.js b/scripts/editing/changeLEDAlpha.js index a5356000e..ebd27770e 100644 --- a/scripts/editing/changeLEDAlpha.js +++ b/scripts/editing/changeLEDAlpha.js @@ -1,20 +1,61 @@ -const fs = require('fs'); -const path = require('path'); - -const data = require("../../../LightTableBackup/2025.03.24.json"); - -for (const [_, controlData] of Object.entries(data.control)) { - let status = controlData.status; - for (let j = 0; j < 5; j++) { - let parts = data.dancer[j].parts; - for (let k = 0; k < parts.length; k++) { - if (parts[k].type === "FIBER" && controlData.status[j][k][1] == 200) { - controlData.status[j][k][1] = 227; +/* + * This script updates FIBER alpha (opacity) values across all frames. + * It changes FIBER parts with alpha value 200 to 227. + * + * The updated data is saved to `scripts/editing/fiber.json`. + */ + +const fs = require("fs"); +const path = require("path"); + +// Configuration +const DANCER_COUNT = 5; +const TARGET_PART_TYPE = "FIBER"; +const OLD_ALPHA = 200; +const NEW_ALPHA = 227; + +const updateFiberAlpha = (data) => { + for (const [frameKey, controlData] of Object.entries(data.control)) { + for (let dancer_id = 0; dancer_id < DANCER_COUNT; dancer_id++) { + if (!data.dancer[dancer_id]) continue; + + const parts = data.dancer[dancer_id].parts; + + for (let part_id = 0; part_id < parts.length; part_id++) { + const part = parts[part_id]; + + // Only process FIBER parts with target alpha value + if ( + part.type === TARGET_PART_TYPE && + controlData.status[dancer_id]?.[part_id]?.[1] === OLD_ALPHA + ) { + controlData.status[dancer_id][part_id][1] = NEW_ALPHA; + } } } } -} +}; -fs.writeFileSync(path.join(__dirname, "./fiber.json"), JSON.stringify(data, null, 0)); +// Main execution +if (require.main === module) { + try { + const inputPath = path.join(__dirname, "../../../LightTableBackup/2025.03.24.json"); + + if (!fs.existsSync(inputPath)) { + throw new Error(`Input file not found: ${inputPath}`); + } + + const data = JSON.parse(fs.readFileSync(inputPath, "utf-8")); + + updateFiberAlpha(data); + + const outputPath = path.join(__dirname, "./fiber.json"); + fs.writeFileSync(outputPath, JSON.stringify(data, null, 2)); + console.log(`✓ Updated fiber alpha values saved to ${outputPath}`); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); + } +} -console.log("saved to ./fiber.json"); \ No newline at end of file +module.exports = { updateFiberAlpha }; \ No newline at end of file diff --git a/scripts/editing/changeLEDAlpha.test.js b/scripts/editing/changeLEDAlpha.test.js new file mode 100644 index 000000000..162139e2b --- /dev/null +++ b/scripts/editing/changeLEDAlpha.test.js @@ -0,0 +1,181 @@ +const { updateFiberAlpha } = require("./changeLEDAlpha"); + +// Mock data for testing +const createMockData = () => ({ + dancer: [ + { + parts: [ + { type: "FIBER", name: "head" }, + { type: "FIBER", name: "arm" }, + { type: "LED", name: "hand" }, + { type: "FIBER", name: "leg" }, + ], + }, + { + parts: [ + { type: "FIBER", name: "head" }, + { type: "LED", name: "arm" }, + ], + }, + { + parts: [ + { type: "FIBER", name: "head" }, + ], + }, + { + parts: [ + { type: "FIBER", name: "torso" }, + ], + }, + { + parts: [ + { type: "FIBER", name: "head" }, + ], + }, + ], + control: { + frame_0: { + status: [ + // Dancer 0 + [ + [255, 200], // FIBER, alpha 200 → should change to 227 + [255, 200], // FIBER, alpha 200 → should change to 227 + [255, 200], // LED, alpha 200 → should NOT change + [255, 100], // FIBER, alpha 100 → should NOT change + ], + // Dancer 1 + [ + [255, 200], // FIBER, alpha 200 → should change to 227 + [255, 200], // LED, alpha 200 → should NOT change + ], + // Dancer 2 + [ + [255, 200], // FIBER, alpha 200 → should change to 227 + ], + // Dancer 3 + [ + [255, 200], // FIBER, alpha 200 → should change to 227 + ], + // Dancer 4 + [ + [255, 200], // FIBER, alpha 200 → should change to 227 + ], + ], + }, + frame_1: { + status: [ + // Dancer 0 + [ + [255, 200], // FIBER, alpha 200 → should change to 227 + [255, 150], // FIBER, alpha 150 → should NOT change + [255, 200], // LED, alpha 200 → should NOT change + [255, 200], // FIBER, alpha 200 → should change to 227 + ], + // Dancer 1 + [ + [255, 200], // FIBER, alpha 200 → should change to 227 + [255, 200], // LED, alpha 200 → should NOT change + ], + // Dancer 2-4 similar + [[255, 200]], + [[255, 200]], + [[255, 200]], + ], + }, + }, +}); + +// Test utilities +const assert = (condition, message) => { + if (!condition) { + console.error(`✗ FAIL: ${message}`); + process.exit(1); + } + console.log(`✓ PASS: ${message}`); +}; + +// Test 1: FIBER parts with alpha 200 are updated +console.log("\n=== Test 1: FIBER alpha 200 → 227 ==="); +const testData1 = createMockData(); +updateFiberAlpha(testData1); + +assert( + testData1.control.frame_0.status[0][0][1] === 227, + "Dancer 0, part 0 (FIBER): alpha 200 → 227" +); +assert( + testData1.control.frame_0.status[0][1][1] === 227, + "Dancer 0, part 1 (FIBER): alpha 200 → 227" +); +assert( + testData1.control.frame_0.status[1][0][1] === 227, + "Dancer 1, part 0 (FIBER): alpha 200 → 227" +); + +// Test 2: LED parts are NOT updated +console.log("\n=== Test 2: LED parts unchanged ==="); +const testData2 = createMockData(); +updateFiberAlpha(testData2); + +assert( + testData2.control.frame_0.status[0][2][1] === 200, + "Dancer 0, part 2 (LED): alpha 200 should remain unchanged" +); +assert( + testData2.control.frame_0.status[1][1][1] === 200, + "Dancer 1, part 1 (LED): alpha 200 should remain unchanged" +); + +// Test 3: FIBER parts with different alpha are NOT updated +console.log("\n=== Test 3: FIBER with different alpha unchanged ==="); +const testData3 = createMockData(); +updateFiberAlpha(testData3); + +assert( + testData3.control.frame_0.status[0][3][1] === 100, + "Dancer 0, part 3 (FIBER): alpha 100 should remain unchanged" +); +assert( + testData3.control.frame_1.status[0][1][1] === 150, + "Dancer 0, part 1 (FIBER): alpha 150 should remain unchanged" +); + +// Test 4: Multiple frames processed +console.log("\n=== Test 4: Multiple frames processed ==="); +const testData4 = createMockData(); +updateFiberAlpha(testData4); + +assert( + testData4.control.frame_1.status[0][0][1] === 227, + "Frame 1, Dancer 0, part 0 (FIBER): alpha 200 → 227" +); +assert( + testData4.control.frame_1.status[0][3][1] === 227, + "Frame 1, Dancer 0, part 3 (FIBER): alpha 200 → 227" +); + +// Test 5: All dancers processed +console.log("\n=== Test 5: All dancers processed ==="); +const testData5 = createMockData(); +updateFiberAlpha(testData5); + +for (let i = 0; i < 5; i++) { + assert( + testData5.control.frame_0.status[i][0][1] === 227, + `Dancer ${i}, part 0 (FIBER): alpha 200 → 227` + ); +} + +// Test 6: Color values unchanged +console.log("\n=== Test 6: Color values unchanged ==="); +const testData6 = createMockData(); +const beforeColor = testData6.control.frame_0.status[0][0][0]; +updateFiberAlpha(testData6); +const afterColor = testData6.control.frame_0.status[0][0][0]; + +assert( + beforeColor === afterColor, + "Color values should not be modified" +); + +console.log("\n=== All tests passed! ===\n"); \ No newline at end of file From 0628bffed33a1386d13fd9d31de96d3582dfa08e Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Sun, 18 Jan 2026 22:10:19 +0800 Subject: [PATCH 03/12] SCRIPTS-#644 rewrote changeLEDLength.js, added some tests. --- scripts/editing/changeLEDLength.js | 337 +++++++++++++++++------- scripts/editing/changeLEDLength.test.js | 210 +++++++++++++++ 2 files changed, 450 insertions(+), 97 deletions(-) create mode 100644 scripts/editing/changeLEDLength.test.js diff --git a/scripts/editing/changeLEDLength.js b/scripts/editing/changeLEDLength.js index 70728726f..47ad8373d 100644 --- a/scripts/editing/changeLEDLength.js +++ b/scripts/editing/changeLEDLength.js @@ -1,112 +1,255 @@ -const oldData = require("./out/exportData.json"); -const newData = require("../../utils/jsons/exportDataEmpty.json"); +/* + * This script updates LED lengths across dancers and adjusts LED effects and status accordingly. + * If a dancer model's LED length changes, all related LED effects and control data are updated. + */ + +const fs = require("fs"); +const path = require("path"); + +const buildModelMap = (dancers) => { + return dancers.reduce((acc, dancer) => { + if (!(dancer.model in acc)) { + acc[dancer.model] = dancer.parts; + } + return acc; + }, {}); +}; -const zip = (a, b) => a.map((k, i) => [k, b[i]]); +const updateLEDEffects = (effects, oldLength, newLength, lastLED) => { + Object.values(effects).forEach((effect) => { + effect.frames.forEach((frame) => { + if (oldLength < newLength) { + // Extend with last LED + frame.LEDs = frame.LEDs.concat( + Array(newLength - oldLength).fill(lastLED) + ); + } else { + // Truncate to new length + frame.LEDs = frame.LEDs.slice(0, newLength); + } + }); + }); +}; -const main = (oldData, newData) => { - const oldModelMap = oldData.dancer.reduce((acc, dancer) => { - if (!(dancer.model in acc)) { - acc[dancer.model] = dancer.parts; - } - return acc; - }, {}); +const updateLEDStatus = (ledStatus, oldLength, newLength) => { + if (ledStatus.length === 0) return; - const newModelMap = newData.dancer.reduce((acc, dancer) => { - if (!(dancer.model in acc)) { - acc[dancer.model] = dancer.parts; - } - return acc; - }, {}); + const lastStatus = ledStatus[ledStatus.length - 1]; - Object.keys(newModelMap).forEach((modelName) => { - if (!(modelName in oldModelMap)) { - console.log(modelName, "not found in new data"); - return; + if (oldLength < newLength) { + // Extend with last status + return ledStatus.concat( + Array(newLength - oldLength).fill(lastStatus) + ); + } else { + // Truncate to new length + return ledStatus.slice(0, newLength); } +}; - const oldDancerParts = oldModelMap[modelName]; - const newDancerParts = newModelMap[modelName]; - - zip(oldDancerParts, newDancerParts).forEach(([oldPart, newPart]) => { - if (oldPart.type !== "LED") { - return; - } - - if (oldPart.length !== newPart.length) { - // console.log( - // `LED length for ${modelName} ${oldPart.name} changed from ${oldPart.length} to ${newPart.length}` - // ); - - // LED Effects - let modelPartEffects = oldData.LEDEffects[modelName][oldPart.name]; - - if (oldPart.length < newPart.length) { - // console.log("Old length is less than new length"); - - Object.keys(modelPartEffects).forEach((effectName) => { - let effect = modelPartEffects[effectName]; - // console.log(effectName); - const lastLED = - effect.frames[0].LEDs[effect.frames[0].LEDs.length - 1]; - effect.frames.forEach((frame) => { - frame.LEDs = frame.LEDs.concat( - Array(newPart.length - oldPart.length).fill(lastLED) - ); - }); - }); - } else { - // console.log("Old length is greater than new length"); - - Object.keys(modelPartEffects).forEach((effectName) => { - let effect = modelPartEffects[effectName]; - effect.frames.forEach((frame) => { - frame.LEDs = frame.LEDs.slice(0, newPart.length); - }); - }); +const changeLEDLength = (oldData, newData) => { + const oldModelMap = buildModelMap(oldData.dancer); + const newModelMap = buildModelMap(newData.dancer); + + // Process each model + Object.entries(newModelMap).forEach(([modelName, newParts]) => { + if (!(modelName in oldModelMap)) { + console.warn(`⚠ Model "${modelName}" not found in old data`); + return; } - // LED bulb data - let dancerIndices = oldData.dancer.reduce((indices, dancer, index) => { - if (dancer.model === modelName) { - indices.push(index); - } - return indices; - }, []); - dancerIndices.forEach((dancerIndex) => { - let partIndex = oldData.dancer[dancerIndex].parts.findIndex( - (part) => part.name === oldPart.name - ); - Object.values(oldData.control).forEach((controlFrame) => { - oldLEDStatus = controlFrame.led_status[dancerIndex][partIndex]; - if (oldLEDStatus.length !== 0) { - if (oldPart.length < newPart.length) { - controlFrame.led_status[dancerIndex][partIndex] = - oldLEDStatus.concat( - Array(newPart.length - oldPart.length).fill( - oldLEDStatus[oldLEDStatus.length - 1] - ) - ); - } else { - controlFrame.led_status[dancerIndex][partIndex] = - oldLEDStatus.slice(0, newPart.length); - } + const oldParts = oldModelMap[modelName]; + + // Process each part + oldParts.forEach((oldPart, partIndex) => { + const newPart = newParts[partIndex]; + + // Only process LED parts with length changes + if (oldPart.type !== "LED" || oldPart.length === newPart.length) { + return; } - }); - }); - oldPart.length = newPart.length; - } - }); - }); + // Get last LED for extending + const modelPartEffects = oldData.LEDEffects[modelName]?.[oldPart.name]; + if (!modelPartEffects) { + console.warn(`⚠ LED effects for ${modelName}.${oldPart.name} not found`); + return; + } - oldData.dancer.forEach((oldDancer) => { - const modelName = oldDancer.model; - oldDancer.parts = oldModelMap[modelName]; - }); + const lastLED = modelPartEffects[Object.keys(modelPartEffects)[0]]?.frames[0]?.LEDs[oldPart.length - 1]; + + // Update LED effects + updateLEDEffects(modelPartEffects, oldPart.length, newPart.length, lastLED); + + // Update LED status in control frames + const dancerIndices = oldData.dancer.reduce((indices, dancer, index) => { + if (dancer.model === modelName) { + indices.push(index); + } + return indices; + }, []); + + dancerIndices.forEach((dancerIndex) => { + const partIdx = oldData.dancer[dancerIndex].parts.findIndex( + (part) => part.name === oldPart.name + ); + + Object.values(oldData.control).forEach((controlFrame) => { + const oldLEDStatus = controlFrame.led_status[dancerIndex][partIdx]; + controlFrame.led_status[dancerIndex][partIdx] = updateLEDStatus( + oldLEDStatus, + oldPart.length, + newPart.length + ); + }); + }); + + // Update part length + oldPart.length = newPart.length; + }); + }); - return oldData; + return oldData; }; -const modifiedData = main(oldData, newData); +// Main execution +if (require.main === module) { + try { + const oldDataPath = path.join(__dirname, "./out/exportData.json"); + const newDataPath = path.join(__dirname, "../../utils/jsons/exportDataEmpty.json"); -console.log(JSON.stringify(modifiedData, null, 2)); + if (!fs.existsSync(oldDataPath)) { + throw new Error(`Old data file not found: ${oldDataPath}`); + } + if (!fs.existsSync(newDataPath)) { + throw new Error(`New data file not found: ${newDataPath}`); + } + + const oldData = JSON.parse(fs.readFileSync(oldDataPath, "utf-8")); + const newData = JSON.parse(fs.readFileSync(newDataPath, "utf-8")); + + const modifiedData = changeLEDLength(oldData, newData); + + const outputPath = path.join(__dirname, "./out/exportData_ledLength_updated.json"); + fs.writeFileSync(outputPath, JSON.stringify(modifiedData, null, 2)); + console.log(`✓ LED lengths updated and saved to ${outputPath}`); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); + } +} + +module.exports = { changeLEDLength, buildModelMap, updateLEDEffects, updateLEDStatus }; + +// const oldData = require("./out/exportData.json"); +// const newData = require("../../utils/jsons/exportDataEmpty.json"); + +// const zip = (a, b) => a.map((k, i) => [k, b[i]]); + +// const main = (oldData, newData) => { +// const oldModelMap = oldData.dancer.reduce((acc, dancer) => { +// if (!(dancer.model in acc)) { +// acc[dancer.model] = dancer.parts; +// } +// return acc; +// }, {}); + +// const newModelMap = newData.dancer.reduce((acc, dancer) => { +// if (!(dancer.model in acc)) { +// acc[dancer.model] = dancer.parts; +// } +// return acc; +// }, {}); + +// Object.keys(newModelMap).forEach((modelName) => { +// if (!(modelName in oldModelMap)) { +// console.log(modelName, "not found in new data"); +// return; +// } + +// const oldDancerParts = oldModelMap[modelName]; +// const newDancerParts = newModelMap[modelName]; + +// zip(oldDancerParts, newDancerParts).forEach(([oldPart, newPart]) => { +// if (oldPart.type !== "LED") { +// return; +// } + +// if (oldPart.length !== newPart.length) { +// // console.log( +// // `LED length for ${modelName} ${oldPart.name} changed from ${oldPart.length} to ${newPart.length}` +// // ); + +// // LED Effects +// let modelPartEffects = oldData.LEDEffects[modelName][oldPart.name]; + +// if (oldPart.length < newPart.length) { +// // console.log("Old length is less than new length"); + +// Object.keys(modelPartEffects).forEach((effectName) => { +// let effect = modelPartEffects[effectName]; +// // console.log(effectName); +// const lastLED = +// effect.frames[0].LEDs[effect.frames[0].LEDs.length - 1]; +// effect.frames.forEach((frame) => { +// frame.LEDs = frame.LEDs.concat( +// Array(newPart.length - oldPart.length).fill(lastLED) +// ); +// }); +// }); +// } else { +// // console.log("Old length is greater than new length"); + +// Object.keys(modelPartEffects).forEach((effectName) => { +// let effect = modelPartEffects[effectName]; +// effect.frames.forEach((frame) => { +// frame.LEDs = frame.LEDs.slice(0, newPart.length); +// }); +// }); +// } + +// // LED bulb data +// let dancerIndices = oldData.dancer.reduce((indices, dancer, index) => { +// if (dancer.model === modelName) { +// indices.push(index); +// } +// return indices; +// }, []); +// dancerIndices.forEach((dancerIndex) => { +// let partIndex = oldData.dancer[dancerIndex].parts.findIndex( +// (part) => part.name === oldPart.name +// ); +// Object.values(oldData.control).forEach((controlFrame) => { +// oldLEDStatus = controlFrame.led_status[dancerIndex][partIndex]; +// if (oldLEDStatus.length !== 0) { +// if (oldPart.length < newPart.length) { +// controlFrame.led_status[dancerIndex][partIndex] = +// oldLEDStatus.concat( +// Array(newPart.length - oldPart.length).fill( +// oldLEDStatus[oldLEDStatus.length - 1] +// ) +// ); +// } else { +// controlFrame.led_status[dancerIndex][partIndex] = +// oldLEDStatus.slice(0, newPart.length); +// } +// } +// }); +// }); + +// oldPart.length = newPart.length; +// } +// }); +// }); + +// oldData.dancer.forEach((oldDancer) => { +// const modelName = oldDancer.model; +// oldDancer.parts = oldModelMap[modelName]; +// }); + +// return oldData; +// }; + +// const modifiedData = main(oldData, newData); + +// console.log(JSON.stringify(modifiedData, null, 2)); diff --git a/scripts/editing/changeLEDLength.test.js b/scripts/editing/changeLEDLength.test.js new file mode 100644 index 000000000..4aae948c9 --- /dev/null +++ b/scripts/editing/changeLEDLength.test.js @@ -0,0 +1,210 @@ +const { changeLEDLength } = require("./changeLEDLength"); + +// Mock data for testing +const createMockData = () => ({ + dancer: [ + { + model: "dancer1", + parts: [ + { type: "LED", name: "arm", length: 10 }, + { type: "FIBER", name: "head", length: 0 }, + ], + }, + { + model: "dancer2", + parts: [ + { type: "LED", name: "leg", length: 8 }, + ], + }, + ], + control: { + frame_0: { + led_status: [ + [ + [[0, 255], [255, 0], [255, 0], [255, 0], [255, 0], [255, 0], [255, 0], [255, 0], [255, 0], [255, 0]], // LED arm, length 10 + [], // FIBER head + ], + [ + [[0, 255], [255, 0], [255, 0], [255, 0], [255, 0], [255, 0], [255, 0], [255, 0]], // LED leg, length 8 + ], + ], + }, + frame_1: { + led_status: [ + [ + [[100, 200], [200, 100], [200, 100], [200, 100], [200, 100], [200, 100], [200, 100], [200, 100], [200, 100], [200, 100]], // LED arm, length 10 + [], + ], + [ + [[100, 200], [200, 100], [200, 100], [200, 100], [200, 100], [200, 100], [200, 100], [200, 100]], // LED leg, length 8 + ], + ], + }, + }, + LEDEffects: { + dancer1: { + arm: { + red: { + repeat: 0, + frames: [ + { + LEDs: [ + [255, 0], + [255, 0], + [255, 0], + [255, 0], + [255, 0], + [255, 0], + [255, 0], + [255, 0], + [255, 0], + [255, 0], + ], + start: 0, + fade: false, + }, + ], + }, + }, + }, + dancer2: { + leg: { + blue: { + repeat: 0, + frames: [ + { + LEDs: [[0, 255], [0, 255], [0, 255], [0, 255], [0, 255], [0, 255], [0, 255], [0, 255]], + start: 0, + fade: false, + }, + ], + }, + }, + }, + }, +}); + +// Test utilities +const assert = (condition, message) => { + if (!condition) { + console.error(`✗ FAIL: ${message}`); + process.exit(1); + } + console.log(`✓ PASS: ${message}`); +}; + +// Test 1: Extend LED length +console.log("\n=== Test 1: Extend LED length (10 → 15) ==="); +const testData1 = createMockData(); +const newData1 = createMockData(); +newData1.dancer[0].parts[0].length = 15; +newData1.dancer[1].parts[0].length = 12; + +changeLEDLength(testData1, newData1); + +assert( + testData1.dancer[0].parts[0].length === 15, + "Dancer 1, arm: LED length updated to 15" +); +assert( + testData1.control.frame_0.led_status[0][0].length === 15, + "Dancer 1, frame 0: LED status extended to 15" +); +assert( + testData1.control.frame_1.led_status[0][0].length === 15, + "Dancer 1, frame 1: LED status extended to 15" +); +assert( + testData1.LEDEffects.dancer1.arm.red.frames[0].LEDs.length === 15, + "LED effect extended to 15" +); +assert( + testData1.LEDEffects.dancer1.arm.red.frames[0].LEDs[14][0] === 255, + "Extended LEDs filled with last LED value" +); + +// Test 2: Truncate LED length +console.log("\n=== Test 2: Truncate LED length (10 → 5) ==="); +const testData2 = createMockData(); +const newData2 = createMockData(); +newData2.dancer[0].parts[0].length = 5; +newData2.dancer[1].parts[0].length = 4; + +changeLEDLength(testData2, newData2); + +assert( + testData2.dancer[0].parts[0].length === 5, + "Dancer 1, arm: LED length updated to 5" +); +assert( + testData2.control.frame_0.led_status[0][0].length === 5, + "Dancer 1, frame 0: LED status truncated to 5" +); +assert( + testData2.control.frame_1.led_status[0][0].length === 5, + "Dancer 1, frame 1: LED status truncated to 5" +); +assert( + testData2.LEDEffects.dancer1.arm.red.frames[0].LEDs.length === 5, + "LED effect truncated to 5" +); + +// Test 3: Unchanged LED length +console.log("\n=== Test 3: Unchanged LED length (10 → 10) ==="); +const testData3 = createMockData(); +const newData3 = createMockData(); + +const originalStatus = JSON.stringify(testData3.control.frame_0.led_status[0][0]); +changeLEDLength(testData3, newData3); +const modifiedStatus = JSON.stringify(testData3.control.frame_0.led_status[0][0]); + +assert( + testData3.dancer[0].parts[0].length === 10, + "Dancer 1, arm: LED length remains 10" +); +assert( + originalStatus === modifiedStatus, + "LED status unchanged when length is same" +); + +// Test 4: Empty LED status not modified +console.log("\n=== Test 4: Empty LED status handling ==="); +const testData4 = createMockData(); +testData4.control.frame_0.led_status[0][1] = []; // FIBER has empty array +const newData4 = createMockData(); +newData4.dancer[0].parts[1].length = 100; // Change length + +changeLEDLength(testData4, newData4); + +assert( + testData4.control.frame_0.led_status[0][1].length === 0, + "Empty LED status remains empty" +); + +// Test 5: Multiple dancers with LED changes +console.log("\n=== Test 5: Multiple dancers updated ==="); +const testData5 = createMockData(); +const newData5 = createMockData(); +newData5.dancer[0].parts[0].length = 12; +newData5.dancer[1].parts[0].length = 10; + +changeLEDLength(testData5, newData5); + +assert( + testData5.dancer[0].parts[0].length === 12, + "Dancer 1 LED length updated" +); +assert( + testData5.dancer[1].parts[0].length === 10, + "Dancer 2 LED length updated" +); +assert( + testData5.control.frame_0.led_status[0][0].length === 12, + "Dancer 1 LED status updated" +); +assert( + testData5.control.frame_0.led_status[1][0].length === 10, + "Dancer 2 LED status updated" +); + +console.log("\n=== All tests passed! ===\n"); \ No newline at end of file From c990d4d726d9bf3e0d3526ac9c86ea9ee64692f1 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Sun, 18 Jan 2026 22:38:50 +0800 Subject: [PATCH 04/12] SCRIPTS-#644 rewrote changePartColor.js and added some tests --- scripts/editing/changePartColor.js | 151 ++++++++++++---- scripts/editing/changePartColor.test.js | 231 ++++++++++++++++++++++++ 2 files changed, 345 insertions(+), 37 deletions(-) create mode 100644 scripts/editing/changePartColor.test.js diff --git a/scripts/editing/changePartColor.js b/scripts/editing/changePartColor.js index 3fd90b6a6..45b2a0246 100644 --- a/scripts/editing/changePartColor.js +++ b/scripts/editing/changePartColor.js @@ -1,53 +1,130 @@ -const fs = require('fs'); -const path = require('path'); +/* + * This script updates part colors for specific dancers during time ranges. + * It replaces old color names with new ones for selected parts. + * + * The modified data is saved to 'props.json'. + */ -// const data = require("./../../LightTableBackup/2025.03.17.json"); -const data = require("../../utils/jsons/exportDataEmpty.json"); +const fs = require("fs"); +const path = require("path"); -const updateFrameForDancer = (key, frame, partLength, LEDColor, index, LEDindex) => { - const status = JSON.parse(JSON.stringify(frame.status)); - const led_status = JSON.parse(JSON.stringify(frame.led_status)); +// Configuration +const COLOR_MAPPING = { + "good_pink": "sky_blue", + "peach_pink": "sky_blue", + "good_Purple": "light_blue", +}; - for (let i = 0; i < DancerLength; i++) { - led_status[index][i] = [] - if (status[index][LEDindex][0] === "good_pink" || status[index][LEDindex][0] === "peach_pink") { - status[index][LEDindex][0] = "sky_blue"; - } - if (status[index][LEDindex][0] === "good_Purple") { - status[index][LEDindex][0] = "light_blue"; - } - } +const updateFrameForDancer = (frame, dancerIndex, partIndex, colorMapping) => { + // Create shallow copies instead of deep clones + const status = frame.status.map((dancer, idx) => + idx === dancerIndex + ? [ + ...dancer.slice(0, partIndex), + updateColor(dancer[partIndex], colorMapping), + ...dancer.slice(partIndex + 1), + ] + : dancer + ); - const controlData = { + const led_status = frame.led_status.map((dancer, idx) => + idx === dancerIndex ? [[], ...dancer.slice(1)] : dancer + ); + + return { ...frame, status, led_status, }; +}; - data.control[key] = controlData; -} +const updateColor = (colorData, colorMapping) => { + const [currentColor, alpha] = colorData; + const newColor = colorMapping[currentColor] || currentColor; + return [newColor, alpha]; +}; + +const changePartColor = ( + data, + startTime, + endTime, + dancerName, + partName, + colorMapping +) => { + // Validate dancer exists + const dancer = data.dancer.find((d) => d.name === dancerName); + if (!dancer) { + throw new Error(`Dancer "${dancerName}" not found`); + } + + // Validate part exists + const part = dancer.parts.find((p) => p.name === partName); + if (!part) { + throw new Error(`Part "${partName}" not found for dancer "${dancerName}"`); + } + + // Get indices + const dancerIndex = data.dancer.findIndex((d) => d.name === dancerName); + const partIndex = dancer.parts.findIndex((p) => p.name === partName); -const editDancerPart = (start, end, LEDColor, index, LEDindex) => { - const existingFrames = Object.keys(data.control).filter((key) => data.control[key].start >= start && data.control[key].start <= end); - existingFrames.forEach((key) => { - updateFrameForDancer(key, data.control[key], partLength, LEDColor, index, LEDindex); + // Find and update frames in time range + const updatedControl = {}; + Object.entries(data.control).forEach(([frameKey, controlFrame]) => { + const frameStart = controlFrame.start || 0; + + // Check if frame is within time range + if (frameStart >= startTime && frameStart <= endTime) { + updatedControl[frameKey] = updateFrameForDancer( + controlFrame, + dancerIndex, + partIndex, + colorMapping + ); + } else { + updatedControl[frameKey] = controlFrame; + } }); -} -let startTime = 0; -let endTime = 5000000; -let dancerName = "0"; -let LEDPart = "mask_LED"; -let LEDColor = ["black", 100]; + return { + ...data, + control: updatedControl, + }; +}; + +// Main execution +if (require.main === module) { + try { + const inputPath = path.join(__dirname, "../../utils/jsons/exportDataEmpty.json"); -let DancerLength = data.dancer.find(d => d.name === dancerName).parts.length; -let partLength = data.dancer.find(d => d.name === dancerName).parts.find(d => d.name === LEDPart).length; -let index = data.dancer.findIndex(d => d.name === dancerName); -let LEDindex = data.dancer[index].parts.findIndex(d => d.name === LEDPart); + if (!fs.existsSync(inputPath)) { + throw new Error(`Input file not found: ${inputPath}`); + } + + const data = JSON.parse(fs.readFileSync(inputPath, "utf-8")); + + // Configuration + const startTime = 0; + const endTime = 5000000; + const dancerName = "0"; + const partName = "mask_LED"; -editDancerPart(startTime, endTime, LEDColor, index, LEDindex); + const modifiedData = changePartColor( + data, + startTime, + endTime, + dancerName, + partName, + COLOR_MAPPING + ); -// fs.writeFileSync(path.join(__dirname, "./../../LightTableBackup/2025.03.17.json"), JSON.stringify(data, null, 0)); -fs.writeFileSync(path.join(__dirname, "./props.json"), JSON.stringify(data, null, 2)); + const outputPath = path.join(__dirname, "./props.json"); + fs.writeFileSync(outputPath, JSON.stringify(modifiedData, null, 2)); + console.log(`✓ Part colors updated and saved to ${outputPath}`); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); + } +} -console.log("Updated data has been saved to ./props.json"); \ No newline at end of file +module.exports = { changePartColor, updateColor, updateFrameForDancer }; \ No newline at end of file diff --git a/scripts/editing/changePartColor.test.js b/scripts/editing/changePartColor.test.js new file mode 100644 index 000000000..e7ee5e76c --- /dev/null +++ b/scripts/editing/changePartColor.test.js @@ -0,0 +1,231 @@ +const { changePartColor, updateColor, updateFrameForDancer } = require("./changePartColor"); + +// Mock data for testing +const createMockData = () => ({ + dancer: [ + { + name: "0", + parts: [ + { name: "mask_LED", length: 10 }, + { name: "head", length: 0 }, + ], + }, + { + name: "1", + parts: [ + { name: "arm_LED", length: 8 }, + ], + }, + ], + control: { + frame_0: { + start: 0, + status: [ + [ + ["good_pink", 255], + ["good_color", 255], + ], + [ + ["peach_pink", 200], + ], + ], + led_status: [ + [[0, 255], [255, 0]], + [[100, 100]], + ], + }, + frame_1: { + start: 2500000, + status: [ + [ + ["good_Purple", 255], + ["good_color", 255], + ], + [ + ["good_pink", 200], + ], + ], + led_status: [ + [[0, 255], [255, 0]], + [[100, 100]], + ], + }, + frame_2: { + start: 6000000, + status: [ + [ + ["good_pink", 255], + ["good_color", 255], + ], + [ + ["good_color", 200], + ], + ], + led_status: [ + [[0, 255], [255, 0]], + [[100, 100]], + ], + }, + }, +}); + +// Test utilities +const assert = (condition, message) => { + if (!condition) { + console.error(`✗ FAIL: ${message}`); + process.exit(1); + } + console.log(`✓ PASS: ${message}`); +}; + +// Test 1: updateColor function +console.log("\n=== Test 1: updateColor function ==="); +const colorMapping = { + "good_pink": "sky_blue", + "peach_pink": "sky_blue", + "good_Purple": "light_blue", +}; + +assert( + updateColor(["good_pink", 255], colorMapping)[0] === "sky_blue", + "good_pink → sky_blue" +); +assert( + updateColor(["good_pink", 255], colorMapping)[1] === 255, + "Alpha value preserved" +); +assert( + updateColor(["good_color", 255], colorMapping)[0] === "good_color", + "Unmapped colors unchanged" +); + +// Test 2: updateFrameForDancer function +console.log("\n=== Test 2: updateFrameForDancer function ==="); +const frame = { + start: 0, + status: [ + [["good_pink", 255], ["good_color", 255]], + [["peach_pink", 200]], + ], + led_status: [ + [[0, 255], [255, 0]], + [[100, 100]], + ], +}; + +const updatedFrame = updateFrameForDancer(frame, 0, 0, colorMapping); + +assert( + updatedFrame.status[0][0][0] === "sky_blue", + "Dancer 0, part 0: good_pink → sky_blue" +); +assert( + updatedFrame.status[0][1][0] === "good_color", + "Dancer 0, part 1: unchanged" +); +assert( + updatedFrame.status[1][0][0] === "peach_pink", + "Dancer 1 not affected" +); + +// Test 3: changePartColor with time range +console.log("\n=== Test 3: changePartColor with time range ==="); +const testData3 = createMockData(); + +const result3 = changePartColor( + testData3, + 0, + 5000000, + "0", + "mask_LED", + colorMapping +); + +assert( + result3.control.frame_0.status[0][0][0] === "sky_blue", + "Frame 0 (start: 0): good_pink → sky_blue" +); +assert( + result3.control.frame_1.status[0][0][0] === "light_blue", + "Frame 1 (start: 2500000): good_Purple → light_blue" +); +assert( + result3.control.frame_2.status[0][0][0] === "good_pink", + "Frame 2 (start: 6000000): outside range, unchanged" +); + +// Test 4: changePartColor for different dancer +console.log("\n=== Test 4: changePartColor for different dancer ==="); +const testData4 = createMockData(); + +const result4 = changePartColor( + testData4, + 0, + 5000000, + "1", + "arm_LED", + colorMapping +); + +assert( + result4.control.frame_0.status[1][0][0] === "sky_blue", + "Dancer 1, part 0 (frame_0): peach_pink → sky_blue (within time range)" +); +assert( + result4.control.frame_1.status[1][0][0] === "sky_blue", + "Dancer 1, part 0 (frame_1): good_pink → sky_blue (within time range)" +); +assert( + result4.control.frame_2.status[1][0][0] === "good_color", + "Dancer 1, part 0 (frame_2): good_color (outside time range, unchanged)" +); + +// Test 5: Error handling - invalid dancer +console.log("\n=== Test 5: Error handling ==="); +const testData5 = createMockData(); + +try { + changePartColor(testData5, 0, 5000000, "invalid_dancer", "mask_LED", colorMapping); + console.error("✗ FAIL: Should throw error for invalid dancer"); + process.exit(1); +} catch (error) { + assert( + error.message.includes("not found"), + "Throws error for invalid dancer" + ); +} + +try { + changePartColor(testData5, 0, 5000000, "0", "invalid_part", colorMapping); + console.error("✗ FAIL: Should throw error for invalid part"); + process.exit(1); +} catch (error) { + assert( + error.message.includes("not found"), + "Throws error for invalid part" + ); +} + +// Test 6: LED status cleared for affected dancer +console.log("\n=== Test 6: LED status handling ==="); +const testData6 = createMockData(); + +const result6 = changePartColor( + testData6, + 0, + 5000000, + "0", + "mask_LED", + colorMapping +); + +assert( + result6.control.frame_0.led_status[0][0].length === 0, + "LED status cleared for affected dancer part" +); +assert( + result6.control.frame_0.led_status[1][0] === testData6.control.frame_0.led_status[1][0], + "LED status unchanged for other dancers" +); + +console.log("\n=== All tests passed! ===\n"); \ No newline at end of file From 459d603aaac510eec23ae3a0ee0fad40b053c050 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Sun, 18 Jan 2026 22:51:08 +0800 Subject: [PATCH 05/12] SCRIPTS-#644 rewrote copyPart.js and added some tests --- scripts/editing/copyPart.js | 126 +++++++++++++--- scripts/editing/copyPart.test.js | 244 +++++++++++++++++++++++++++++++ 2 files changed, 352 insertions(+), 18 deletions(-) create mode 100644 scripts/editing/copyPart.test.js diff --git a/scripts/editing/copyPart.js b/scripts/editing/copyPart.js index 213809aff..85d3dd29c 100644 --- a/scripts/editing/copyPart.js +++ b/scripts/editing/copyPart.js @@ -1,23 +1,113 @@ -const fs = require('fs'); -const path = require('path'); +/* + * This script copies part status and LED status from one frame to all frames within a time range. + * Useful for applying the same part configuration across multiple frames. + */ -const data = require("./props.json"); -console.log(Object.keys(data.control).length) +const fs = require("fs"); +const path = require("path"); -let copyFrame = Object.entries(data.control).find(([_, value]) => value.start == 463344)[1]; -let copyLEDStatus = copyFrame.led_status[0]; -let copyStatus = copyFrame.status[0]; -let start = 463812; -let end = 100000000; +// Configuration +const SOURCE_FRAME_START_TIME = 463344; +const TARGET_START_TIME = 463812; +const TARGET_END_TIME = 100000000; +const DANCER_INDEX = 0; +const findFrameByStartTime = (control, startTime) => { + const entry = Object.entries(control).find( + ([_, frame]) => frame.start === startTime + ); + return entry ? entry[1] : null; +}; -let existingFrames = Object.keys(data.control).filter((key) => data.control[key].start >= start && data.control[key].start <= end); -existingFrames.forEach((key) => { - data.control[key].status[0] = copyStatus; - data.control[key].led_status[0] = copyLEDStatus -}); - +const getFramesInTimeRange = (control, startTime, endTime) => { + return Object.entries(control).filter( + ([_, frame]) => frame.start >= startTime && frame.start <= endTime + ); +}; -fs.writeFileSync(path.join(__dirname, "./props.json"), JSON.stringify(data, null, 0)); -console.log(Object.keys(data.control).length) -console.log("saved to ./props.json") \ No newline at end of file +const copyPart = (data, sourceStartTime, targetStartTime, targetEndTime, dancerIndex) => { + // Validate source frame exists + const sourceFrame = findFrameByStartTime(data.control, sourceStartTime); + if (!sourceFrame) { + throw new Error(`Source frame with start time ${sourceStartTime} not found`); + } + + // Validate dancer index + if (dancerIndex >= data.dancer.length) { + throw new Error(`Dancer index ${dancerIndex} out of range (max: ${data.dancer.length - 1})`); + } + + // Validate parts exist + if (!sourceFrame.status[dancerIndex]) { + throw new Error(`Status for dancer ${dancerIndex} not found in source frame`); + } + if (!sourceFrame.led_status[dancerIndex]) { + throw new Error(`LED status for dancer ${dancerIndex} not found in source frame`); + } + + // Extract source data + const sourceStatus = sourceFrame.status[dancerIndex]; + const sourceLEDStatus = sourceFrame.led_status[dancerIndex]; + + // Get frames in target time range + const targetFrames = getFramesInTimeRange(data.control, targetStartTime, targetEndTime); + + if (targetFrames.length === 0) { + console.warn(`⚠ No frames found in time range [${targetStartTime}, ${targetEndTime}]`); + } + + // Apply source data to all target frames + const updatedControl = { ...data.control }; + + targetFrames.forEach(([frameKey, targetFrame]) => { + updatedControl[frameKey] = { + ...targetFrame, + status: [ + ...targetFrame.status.slice(0, dancerIndex), + sourceStatus, + ...targetFrame.status.slice(dancerIndex + 1), + ], + led_status: [ + ...targetFrame.led_status.slice(0, dancerIndex), + sourceLEDStatus, + ...targetFrame.led_status.slice(dancerIndex + 1), + ], + }; + }); + + return { + ...data, + control: updatedControl, + }; +}; + +// Main execution +if (require.main === module) { + try { + const inputPath = path.join(__dirname, "./props.json"); + + if (!fs.existsSync(inputPath)) { + throw new Error(`Input file not found: ${inputPath}`); + } + + const data = JSON.parse(fs.readFileSync(inputPath, "utf-8")); + + const modifiedData = copyPart( + data, + SOURCE_FRAME_START_TIME, + TARGET_START_TIME, + TARGET_END_TIME, + DANCER_INDEX + ); + + const outputPath = path.join(__dirname, "./props.json"); + fs.writeFileSync(outputPath, JSON.stringify(modifiedData, null, 2)); + console.log(`✓ Part copied from frame ${SOURCE_FRAME_START_TIME} to frames [${TARGET_START_TIME}, ${TARGET_END_TIME}]`); + console.log(`✓ Total frames processed: ${Object.keys(data.control).length}`); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); + } +} + +module.exports = { copyPart, findFrameByStartTime, getFramesInTimeRange }; \ No newline at end of file diff --git a/scripts/editing/copyPart.test.js b/scripts/editing/copyPart.test.js new file mode 100644 index 000000000..0f9db0280 --- /dev/null +++ b/scripts/editing/copyPart.test.js @@ -0,0 +1,244 @@ +const { copyPart, findFrameByStartTime, getFramesInTimeRange } = require("./copyPart"); + +// Mock data for testing +const createMockData = () => ({ + dancer: [ + { + name: "0", + parts: [ + { name: "arm", length: 10 }, + { name: "head", length: 0 }, + ], + }, + { + name: "1", + parts: [ + { name: "leg", length: 8 }, + ], + }, + ], + control: { + frame_0: { + start: 0, + status: [ + [["red", 255], ["blue", 255]], + [["green", 200]], + ], + led_status: [ + [[255, 0], [0, 255]], + [[100, 100]], + ], + }, + frame_1: { + start: 463344, + status: [ + [["pink", 255], ["yellow", 255]], + [["orange", 200]], + ], + led_status: [ + [[200, 50], [50, 200]], + [[150, 150]], + ], + }, + frame_2: { + start: 463812, + status: [ + [["old_color_1", 255], ["old_color_2", 255]], + [["old_color_3", 200]], + ], + led_status: [ + [[10, 20], [30, 40]], + [[60, 70]], + ], + }, + frame_3: { + start: 500000, + status: [ + [["old_color_1", 255], ["old_color_2", 255]], + [["old_color_3", 200]], + ], + led_status: [ + [[10, 20], [30, 40]], + [[60, 70]], + ], + }, + frame_4: { + start: 100000000, + status: [ + [["old_color_1", 255], ["old_color_2", 255]], + [["old_color_3", 200]], + ], + led_status: [ + [[10, 20], [30, 40]], + [[60, 70]], + ], + }, + frame_5: { + start: 100000001, + status: [ + [["should_not_change", 255], ["should_not_change", 255]], + [["should_not_change", 200]], + ], + led_status: [ + [[99, 99], [99, 99]], + [[99, 99]], + ], + }, + }, +}); + +// Test utilities +const assert = (condition, message) => { + if (!condition) { + console.error(`✗ FAIL: ${message}`); + process.exit(1); + } + console.log(`✓ PASS: ${message}`); +}; + +// Test 1: findFrameByStartTime +console.log("\n=== Test 1: findFrameByStartTime ==="); +const testData1 = createMockData(); + +const foundFrame = findFrameByStartTime(testData1.control, 463344); +assert( + foundFrame && foundFrame.start === 463344, + "Found frame with start time 463344" +); + +const notFound = findFrameByStartTime(testData1.control, 999999); +assert( + notFound === null, + "Returns null for non-existent frame" +); + +// Test 2: getFramesInTimeRange +console.log("\n=== Test 2: getFramesInTimeRange ==="); +const testData2 = createMockData(); + +const framesInRange = getFramesInTimeRange(testData2.control, 463812, 100000000); +assert( + framesInRange.length === 3, + "Found 3 frames in range [463812, 100000000]" +); + +const framesOutOfRange = getFramesInTimeRange(testData2.control, 999999, 999999); +assert( + framesOutOfRange.length === 0, + "Returns empty array for out-of-range times" +); + +// Test 3: copyPart - basic functionality +console.log("\n=== Test 3: copyPart - basic functionality ==="); +const testData3 = createMockData(); + +const result3 = copyPart(testData3, 463344, 463812, 100000000, 0); + +assert( + result3.control.frame_2.status[0][0][0] === "pink", + "Frame 2, dancer 0, part 0: status copied (old_color_1 → pink)" +); +assert( + result3.control.frame_2.status[0][1][0] === "yellow", + "Frame 2, dancer 0, part 1: status copied (old_color_2 → yellow)" +); +assert( + result3.control.frame_3.status[0][0][0] === "pink", + "Frame 3, dancer 0, part 0: status copied" +); +assert( + result3.control.frame_4.status[0][0][0] === "pink", + "Frame 4, dancer 0, part 0: status copied (end of range)" +); + +// Test 4: LED status copied +console.log("\n=== Test 4: LED status copied ==="); +const testData4 = createMockData(); + +const result4 = copyPart(testData4, 463344, 463812, 100000000, 0); + +assert( + JSON.stringify(result4.control.frame_2.led_status[0]) === JSON.stringify([[200, 50], [50, 200]]), + "Frame 2: LED status copied from source frame" +); +assert( + JSON.stringify(result4.control.frame_3.led_status[0]) === JSON.stringify([[200, 50], [50, 200]]), + "Frame 3: LED status copied from source frame" +); + +// Test 5: Other dancers not affected +console.log("\n=== Test 5: Other dancers not affected ==="); +const testData5 = createMockData(); + +const result5 = copyPart(testData5, 463344, 463812, 100000000, 0); + +assert( + result5.control.frame_2.status[1][0][0] === "old_color_3", + "Dancer 1 not affected" +); +assert( + JSON.stringify(result5.control.frame_2.led_status[1]) === JSON.stringify([[60, 70]]), + "Dancer 1 LED status not affected" +); + +// Test 6: Frames outside range not affected +console.log("\n=== Test 6: Frames outside range not affected ==="); +const testData6 = createMockData(); + +const result6 = copyPart(testData6, 463344, 463812, 100000000, 0); + +assert( + result6.control.frame_0.status[0][0][0] === "red", + "Frame 0 (before range): not changed" +); +assert( + result6.control.frame_5.status[0][0][0] === "should_not_change", + "Frame 5 (after range): not changed" +); + +// Test 7: Error handling - invalid source frame +console.log("\n=== Test 7: Error handling ==="); +const testData7 = createMockData(); + +try { + copyPart(testData7, 999999, 463812, 100000000, 0); + console.error("✗ FAIL: Should throw error for invalid source frame"); + process.exit(1); +} catch (error) { + assert( + error.message.includes("not found"), + "Throws error for invalid source frame" + ); +} + +// Test 8: Error handling - invalid dancer index +console.log("\n=== Test 8: Error handling - invalid dancer ==="); +const testData8 = createMockData(); + +try { + copyPart(testData8, 463344, 463812, 100000000, 999); + console.error("✗ FAIL: Should throw error for invalid dancer"); + process.exit(1); +} catch (error) { + assert( + error.message.includes("out of range"), + "Throws error for invalid dancer index" + ); +} + +// Test 9: Copy to different dancer +console.log("\n=== Test 9: Copy to different dancer ==="); +const testData9 = createMockData(); + +const result9 = copyPart(testData9, 463344, 463812, 100000000, 1); + +assert( + result9.control.frame_2.status[1][0][0] === "orange", + "Frame 2, dancer 1: status copied from source dancer 1" +); +assert( + result9.control.frame_2.status[0][0][0] === "old_color_1", + "Dancer 0 not affected when copying to dancer 1" +); + +console.log("\n=== All tests passed! ===\n"); \ No newline at end of file From e787bf21809be6762c44b9b7e952fdb0e3a95770 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Sun, 18 Jan 2026 23:03:07 +0800 Subject: [PATCH 06/12] rewrote deleteFrames.js and added some tests --- scripts/editing/deleteFrames.js | 117 ++++++++++++-- scripts/editing/deleteFrames.test.js | 225 +++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 17 deletions(-) create mode 100644 scripts/editing/deleteFrames.test.js diff --git a/scripts/editing/deleteFrames.js b/scripts/editing/deleteFrames.js index ab179fa1d..375246be3 100644 --- a/scripts/editing/deleteFrames.js +++ b/scripts/editing/deleteFrames.js @@ -1,23 +1,106 @@ -const fs = require('fs'); -const path = require('path'); +/* + * This script deletes frames within a specified time range. + * Frames with start times strictly between startTime and endTime are removed. + */ -const data = require("../../../LightTableBackup/2025.03.23.json"); -console.log(Object.keys(data.control).length) -const startTime = 426000; -const endTime = 433625; +const fs = require("fs"); +const path = require("path"); -for (const key of Object.keys(data.control)) { - if (data.control[key].start > startTime && data.control[key].start < endTime) { - delete data.control[key]; +// Configuration +const START_TIME = 426000; +const END_TIME = 433625; +const DELETE_CONTROL = true; +const DELETE_POSITION = false; + +const deleteFramesInRange = (data, startTime, endTime, deleteControl = true, deletePosition = false) => { + // Validate time range + if (startTime >= endTime) { + throw new Error(`Invalid time range: startTime (${startTime}) must be less than endTime (${endTime})`); } -} -// for (const key of Object.keys(data.position)) { -// if (data.position[key].start > startTime && data.position[key].start < endTime) { -// delete data.position[key]; -// } -// } + const updatedData = { ...data }; + let deletedControlCount = 0; + let deletedPositionCount = 0; + + // Delete frames from control + if (deleteControl && data.control) { + const updatedControl = {}; + + Object.entries(data.control).forEach(([frameKey, frame]) => { + const frameStart = frame.start; + + // Delete if frame is strictly within range (exclusive) + if (frameStart > startTime && frameStart < endTime) { + deletedControlCount++; + } else { + updatedControl[frameKey] = frame; + } + }); + + updatedData.control = updatedControl; + } + + // Delete frames from position + if (deletePosition && data.position) { + const updatedPosition = {}; + + Object.entries(data.position).forEach(([frameKey, frame]) => { + const frameStart = frame.start; + + if (frameStart > startTime && frameStart < endTime) { + deletedPositionCount++; + } else { + updatedPosition[frameKey] = frame; + } + }); + + updatedData.position = updatedPosition; + } -console.log(Object.keys(data.control).length) + return { + data: updatedData, + deletedControlCount, + deletedPositionCount, + }; +}; + +// Main execution +if (require.main === module) { + try { + const inputPath = path.join(__dirname, "../../../LightTableBackup/2025.03.23.json"); + + if (!fs.existsSync(inputPath)) { + throw new Error(`Input file not found: ${inputPath}`); + } + + const data = JSON.parse(fs.readFileSync(inputPath, "utf-8")); + + const beforeControlCount = Object.keys(data.control || {}).length; + const beforePositionCount = Object.keys(data.position || {}).length; + + console.log(`Before: ${beforeControlCount} control frames, ${beforePositionCount} position frames`); + + const result = deleteFramesInRange( + data, + START_TIME, + END_TIME, + DELETE_CONTROL, + DELETE_POSITION + ); + + const afterControlCount = Object.keys(result.data.control || {}).length; + const afterPositionCount = Object.keys(result.data.position || {}).length; + + console.log(`After: ${afterControlCount} control frames, ${afterPositionCount} position frames`); + console.log(`Deleted: ${result.deletedControlCount} control frames, ${result.deletedPositionCount} position frames`); + + const outputPath = path.join(__dirname, "./deleted.json"); + fs.writeFileSync(outputPath, JSON.stringify(result.data, null, 2)); + console.log(`✓ Frames deleted and saved to ${outputPath}`); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); + } +} -fs.writeFileSync(path.join(__dirname, "./deleted.json"), JSON.stringify(data, null, 0)); \ No newline at end of file +module.exports = { deleteFramesInRange }; \ No newline at end of file diff --git a/scripts/editing/deleteFrames.test.js b/scripts/editing/deleteFrames.test.js new file mode 100644 index 000000000..64fd8d50a --- /dev/null +++ b/scripts/editing/deleteFrames.test.js @@ -0,0 +1,225 @@ +const { deleteFramesInRange } = require("./deleteFrames"); + +// Mock data for testing +const createMockData = () => ({ + control: { + frame_0: { start: 400000, data: "control_0" }, + frame_1: { start: 426500, data: "control_1" }, // within range + frame_2: { start: 430000, data: "control_2" }, // within range + frame_3: { start: 433600, data: "control_3" }, // within range + frame_4: { start: 440000, data: "control_4" }, + frame_5: { start: 426000, data: "control_5" }, // at start boundary (not deleted) + frame_6: { start: 433625, data: "control_6" }, // at end boundary (not deleted) + }, + position: { + frame_0: { start: 400000, data: "position_0" }, + frame_1: { start: 426500, data: "position_1" }, // within range + frame_2: { start: 430000, data: "position_2" }, // within range + frame_3: { start: 433600, data: "position_3" }, // within range + frame_4: { start: 440000, data: "position_4" }, + frame_5: { start: 426000, data: "position_5" }, // at start boundary (not deleted) + frame_6: { start: 433625, data: "position_6" }, // at end boundary (not deleted) + }, +}); + +// Test utilities +const assert = (condition, message) => { + if (!condition) { + console.error(`✗ FAIL: ${message}`); + process.exit(1); + } + console.log(`✓ PASS: ${message}`); +}; + +// Test 1: Delete control frames in range +console.log("\n=== Test 1: Delete control frames in range ==="); +const testData1 = createMockData(); + +const result1 = deleteFramesInRange(testData1, 426000, 433625, true, false); + +assert( + result1.deletedControlCount === 3, + "Deleted 3 control frames (426500, 430000, 433600)" +); +assert( + Object.keys(result1.data.control).length === 4, + "4 control frames remaining (3 deleted + 2 boundaries + 1 outside)" +); +assert( + result1.data.control.frame_0.start === 400000, + "Frame before range preserved" +); +assert( + result1.data.control.frame_4.start === 440000, + "Frame after range preserved" +); +assert( + result1.data.control.frame_5.start === 426000, + "Frame at start boundary preserved (exclusive)" +); +assert( + result1.data.control.frame_6.start === 433625, + "Frame at end boundary preserved (exclusive)" +); + +// Test 2: Delete position frames +console.log("\n=== Test 2: Delete position frames ==="); +const testData2 = createMockData(); + +const result2 = deleteFramesInRange(testData2, 426000, 433625, false, true); + +assert( + result2.deletedPositionCount === 3, + "Deleted 3 position frames" +); +assert( + result2.deletedControlCount === 0, + "No control frames deleted" +); +assert( + Object.keys(result2.data.position).length === 4, + "4 position frames remaining" +); + +// Test 3: Delete both control and position +console.log("\n=== Test 3: Delete both control and position ==="); +const testData3 = createMockData(); + +const result3 = deleteFramesInRange(testData3, 426000, 433625, true, true); + +assert( + result3.deletedControlCount === 3, + "Deleted 3 control frames" +); +assert( + result3.deletedPositionCount === 3, + "Deleted 3 position frames" +); +assert( + Object.keys(result3.data.control).length === 4, + "4 control frames remaining" +); +assert( + Object.keys(result3.data.position).length === 4, + "4 position frames remaining" +); + +// Test 4: Delete neither +console.log("\n=== Test 4: Delete neither ==="); +const testData4 = createMockData(); + +const result4 = deleteFramesInRange(testData4, 426000, 433625, false, false); + +assert( + result4.deletedControlCount === 0, + "No control frames deleted" +); +assert( + result4.deletedPositionCount === 0, + "No position frames deleted" +); +assert( + Object.keys(result4.data.control).length === 7, + "All control frames preserved" +); +assert( + Object.keys(result4.data.position).length === 7, + "All position frames preserved" +); + +// Test 5: Empty range (no frames deleted) +console.log("\n=== Test 5: Empty range ==="); +const testData5 = createMockData(); + +const result5 = deleteFramesInRange(testData5, 427000, 427100, true, false); + +assert( + result5.deletedControlCount === 0, + "No frames deleted in empty range" +); +assert( + Object.keys(result5.data.control).length === 7, + "All control frames preserved" +); + +// Test 6: Large range (delete many frames) +console.log("\n=== Test 6: Large range ==="); +const testData6 = createMockData(); + +const result6 = deleteFramesInRange(testData6, 400001, 439999, true, true); + +assert( + result6.deletedControlCount === 5, + "Deleted 5 control frames (all except boundaries)" +); +assert( + result6.deletedPositionCount === 5, + "Deleted 5 position frames (all except boundaries)" +); +assert( + Object.keys(result6.data.control).length === 2, + "2 control frames remaining (at boundaries)" +); + +// Test 7: Error handling - invalid range +console.log("\n=== Test 7: Error handling ==="); +const testData7 = createMockData(); + +try { + deleteFramesInRange(testData7, 433625, 426000, true, false); + console.error("✗ FAIL: Should throw error for invalid range"); + process.exit(1); +} catch (error) { + assert( + error.message.includes("Invalid time range"), + "Throws error when startTime >= endTime" + ); +} + +// Test 8: Equal start and end times +console.log("\n=== Test 8: Equal times ==="); +const testData8 = createMockData(); + +try { + deleteFramesInRange(testData8, 426000, 426000, true, false); + console.error("✗ FAIL: Should throw error for equal times"); + process.exit(1); +} catch (error) { + assert( + error.message.includes("Invalid time range"), + "Throws error when startTime === endTime" + ); +} + +// Test 9: Boundary conditions (exclusive range) +console.log("\n=== Test 9: Boundary conditions ==="); +const testData9 = createMockData(); + +const result9 = deleteFramesInRange(testData9, 426000, 433625, true, false); + +assert( + result9.data.control.frame_5 !== undefined && result9.data.control.frame_5.start === 426000, + "Frame at start boundary (426000) is preserved" +); +assert( + result9.data.control.frame_6 !== undefined && result9.data.control.frame_6.start === 433625, + "Frame at end boundary (433625) is preserved" +); +assert( + result9.deletedControlCount === 3, + "Exactly 3 frames deleted (exclusive boundaries)" +); + +// Test 10: Original data not modified +console.log("\n=== Test 10: Original data not modified ==="); +const testData10 = createMockData(); +const originalLength = Object.keys(testData10.control).length; + +deleteFramesInRange(testData10, 426000, 433625, true, true); + +assert( + Object.keys(testData10.control).length === originalLength, + "Original data not modified" +); + +console.log("\n=== All tests passed! ===\n"); \ No newline at end of file From d9512889e1d4970f502b17493b11347a7654c753 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Sun, 18 Jan 2026 23:10:10 +0800 Subject: [PATCH 07/12] modified script description --- scripts/editing/deleteFrames.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/editing/deleteFrames.js b/scripts/editing/deleteFrames.js index 375246be3..fbf17aaca 100644 --- a/scripts/editing/deleteFrames.js +++ b/scripts/editing/deleteFrames.js @@ -1,6 +1,8 @@ /* * This script deletes frames within a specified time range. * Frames with start times strictly between startTime and endTime are removed. + * + * The modified data is saved to 'deleted.json'. */ const fs = require("fs"); From 27d322790c40b41fbd967cb5d452c6c63f52fb64 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Sun, 18 Jan 2026 23:11:04 +0800 Subject: [PATCH 08/12] SCRIPTS-#644 rewrote shiftFrames.js and added some tests --- scripts/editing/shiftFrames.js | 164 +++++++++++++------- scripts/editing/shiftFrames.test.js | 231 ++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+), 53 deletions(-) create mode 100644 scripts/editing/shiftFrames.test.js diff --git a/scripts/editing/shiftFrames.js b/scripts/editing/shiftFrames.js index ac0b3c1f7..37511c14b 100644 --- a/scripts/editing/shiftFrames.js +++ b/scripts/editing/shiftFrames.js @@ -1,71 +1,129 @@ -const fs = require('fs'); -const path = require('path'); +/* + * This script shifts frame start times within specified time ranges. + * Multiple shift operations can be applied sequentially. + * Useful for adjusting timing across different sections of a performance. + * + * The modified data is saved to 'shifted.json'. + */ -const data = require("./saber.json"); +const fs = require("fs"); +const path = require("path"); -startTime = 336007; -endTime = 6000000; -shift = 12857; +// Configuration - array of shifts to apply in order +const FRAME_SHIFTS = [ + { startTime: 336007, endTime: 6000000, shift: 12857 }, + { startTime: 215928, endTime: 336007, shift: 10050 }, + { startTime: 157289, endTime: 215928, shift: 7294 }, + { startTime: 98125, endTime: 157289, shift: 3750 }, +]; -for (const key of Object.keys(data.control)) { - if (data.control[key].start > startTime && data.control[key].start < endTime) { - data.control[key].start += shift; +const shiftFramesInRange = (frames, startTime, endTime, shiftAmount) => { + if (!frames) return frames; + + // Validate inputs + if (startTime >= endTime) { + throw new Error(`Invalid time range: startTime (${startTime}) must be less than endTime (${endTime})`); } -} -for (const key of Object.keys(data.position)) { - if (data.position[key].start > startTime && data.position[key].start < endTime) { - data.position[key].start += shift; + if (shiftAmount === 0) { + console.warn("⚠ Shift amount is 0, no changes will be made"); } -} -startTime = 215928; -endTime = 336007; -shift = 10050; + let shiftedCount = 0; -for (const key of Object.keys(data.control)) { - if (data.control[key].start > startTime && data.control[key].start < endTime) { - data.control[key].start += shift; - } -} + const updatedFrames = {}; -for (const key of Object.keys(data.position)) { - if (data.position[key].start > startTime && data.position[key].start < endTime) { - data.position[key].start += shift; - } -} + Object.entries(frames).forEach(([frameKey, frame]) => { + const frameStart = frame.start; -startTime = 157289; -endTime = 215928; -shift = 7294; + // Shift if frame is strictly within range (exclusive) + if (frameStart > startTime && frameStart < endTime) { + updatedFrames[frameKey] = { + ...frame, + start: frameStart + shiftAmount, + }; + shiftedCount++; + } else { + updatedFrames[frameKey] = frame; + } + }); -for (const key of Object.keys(data.control)) { - if (data.control[key].start > startTime && data.control[key].start < endTime) { - data.control[key].start += shift; - } -} + return { frames: updatedFrames, shiftedCount }; +}; -for (const key of Object.keys(data.position)) { - if (data.position[key].start > startTime && data.position[key].start < endTime) { - data.position[key].start += shift; - } -} +const applyMultipleShifts = (data, shifts) => { + let updatedData = { ...data }; + const results = []; -startTime = 98125; -endTime = 157289; -shift = 3750; + shifts.forEach((shiftConfig, index) => { + const { startTime, endTime, shift } = shiftConfig; -for (const key of Object.keys(data.control)) { - if (data.control[key].start > startTime && data.control[key].start < endTime) { - data.control[key].start += shift; - } -} + console.log(`\nApplying shift ${index + 1}/${shifts.length}...`); + console.log(` Range: [${startTime}, ${endTime}], Shift: ${shift}`); + + // Shift control frames + const controlResult = shiftFramesInRange( + updatedData.control, + startTime, + endTime, + shift + ); + updatedData.control = controlResult.frames; + + // Shift position frames + const positionResult = shiftFramesInRange( + updatedData.position, + startTime, + endTime, + shift + ); + updatedData.position = positionResult.frames; + + results.push({ + shiftConfig, + controlShifted: controlResult.shiftedCount, + positionShifted: positionResult.shiftedCount, + }); + + console.log(` Shifted: ${controlResult.shiftedCount} control, ${positionResult.shiftedCount} position frames`); + }); + + return { data: updatedData, results }; +}; + +// Main execution +if (require.main === module) { + try { + const inputPath = path.join(__dirname, "./saber.json"); + + if (!fs.existsSync(inputPath)) { + throw new Error(`Input file not found: ${inputPath}`); + } + + const data = JSON.parse(fs.readFileSync(inputPath, "utf-8")); + + console.log(`Starting with ${Object.keys(data.control || {}).length} control frames and ${Object.keys(data.position || {}).length} position frames`); + + const { data: modifiedData, results } = applyMultipleShifts(data, FRAME_SHIFTS); + + let totalControlShifted = 0; + let totalPositionShifted = 0; + + results.forEach((result) => { + totalControlShifted += result.controlShifted; + totalPositionShifted += result.positionShifted; + }); + + console.log(`\n✓ Total shifted: ${totalControlShifted} control frames, ${totalPositionShifted} position frames`); + console.log(`Final: ${Object.keys(modifiedData.control || {}).length} control frames and ${Object.keys(modifiedData.position || {}).length} position frames`); -for (const key of Object.keys(data.position)) { - if (data.position[key].start > startTime && data.position[key].start < endTime) { - data.position[key].start += shift; + const outputPath = path.join(__dirname, "./shifted.json"); + fs.writeFileSync(outputPath, JSON.stringify(modifiedData, null, 2)); + console.log(`✓ Shifted frames saved to ${outputPath}`); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); } } -fs.writeFileSync(path.join(__dirname, "./shifted.json"), JSON.stringify(data, null, 0)); -console.log("saved to ./shifted.json") \ No newline at end of file +module.exports = { shiftFramesInRange, applyMultipleShifts }; \ No newline at end of file diff --git a/scripts/editing/shiftFrames.test.js b/scripts/editing/shiftFrames.test.js new file mode 100644 index 000000000..9adbd16dc --- /dev/null +++ b/scripts/editing/shiftFrames.test.js @@ -0,0 +1,231 @@ +const { shiftFramesInRange, applyMultipleShifts } = require("./shiftFrames"); + +// Mock data for testing +const createMockData = () => ({ + control: { + frame_0: { start: 50000, data: "control_0" }, + frame_1: { start: 100000, data: "control_1" }, // shift +3750 + frame_2: { start: 120000, data: "control_2" }, // shift +3750 + frame_3: { start: 157289, data: "control_3" }, // boundary + frame_4: { start: 180000, data: "control_4" }, // shift +7294 + frame_5: { start: 215928, data: "control_5" }, // boundary + frame_6: { start: 250000, data: "control_6" }, // shift +10050 + frame_7: { start: 336007, data: "control_7" }, // boundary + frame_8: { start: 500000, data: "control_8" }, // shift +12857 + frame_9: { start: 6000000, data: "control_9" }, // boundary + frame_10: { start: 7000000, data: "control_10" }, + }, + position: { + frame_0: { start: 50000, data: "position_0" }, + frame_1: { start: 100000, data: "position_1" }, // shift +3750 + frame_2: { start: 120000, data: "position_2" }, // shift +3750 + frame_3: { start: 157289, data: "position_3" }, // boundary + frame_4: { start: 180000, data: "position_4" }, // shift +7294 + frame_5: { start: 215928, data: "position_5" }, // boundary + frame_6: { start: 250000, data: "position_6" }, // shift +10050 + frame_7: { start: 336007, data: "position_7" }, // boundary + frame_8: { start: 500000, data: "position_8" }, // shift +12857 + frame_9: { start: 6000000, data: "position_9" }, // boundary + frame_10: { start: 7000000, data: "position_10" }, + }, +}); + +// Test utilities +const assert = (condition, message) => { + if (!condition) { + console.error(`✗ FAIL: ${message}`); + process.exit(1); + } + console.log(`✓ PASS: ${message}`); +}; + +// Test 1: shiftFramesInRange - basic shift +console.log("\n=== Test 1: shiftFramesInRange - basic shift ==="); +const testData1 = createMockData(); + +const result1 = shiftFramesInRange(testData1.control, 98125, 157289, 3750); + +assert( + result1.shiftedCount === 2, + "Shifted 2 frames (100000, 120000)" +); +assert( + result1.frames.frame_1.start === 103750, + "Frame 1: 100000 + 3750 = 103750" +); +assert( + result1.frames.frame_2.start === 123750, + "Frame 2: 120000 + 3750 = 123750" +); +assert( + result1.frames.frame_0.start === 50000, + "Frame 0 (before range): unchanged" +); +assert( + result1.frames.frame_3.start === 157289, + "Frame 3 (at boundary): unchanged (exclusive)" +); + +// Test 2: Boundary conditions (exclusive range) +console.log("\n=== Test 2: Boundary conditions ==="); +const testData2 = createMockData(); + +const result2 = shiftFramesInRange(testData2.control, 98125, 157289, 3750); + +assert( + result2.frames.frame_1.start === 103750, + "Frame with start 100000 (> 98125) shifted" +); +assert( + result2.frames.frame_3.start === 157289, + "Frame with start 157289 (not < 157289) not shifted" +); + +// Test 3: Negative shift +console.log("\n=== Test 3: Negative shift ==="); +const testData3 = createMockData(); + +const result3 = shiftFramesInRange(testData3.control, 98125, 157289, -1000); + +assert( + result3.shiftedCount === 2, + "Shifted 2 frames with negative shift" +); +assert( + result3.frames.frame_1.start === 99000, + "Frame 1: 100000 - 1000 = 99000" +); + +// Test 4: Zero shift +console.log("\n=== Test 4: Zero shift ==="); +const testData4 = createMockData(); + +const result4 = shiftFramesInRange(testData4.control, 98125, 157289, 0); + +assert( + result4.shiftedCount === 2, + "Still counts frames even with 0 shift" +); +assert( + result4.frames.frame_1.start === 100000, + "Frame unchanged with 0 shift" +); + +// Test 5: Empty range (no frames to shift) +console.log("\n=== Test 5: Empty range ==="); +const testData5 = createMockData(); + +const result5 = shiftFramesInRange(testData5.control, 130000, 140000, 1000); + +assert( + result5.shiftedCount === 0, + "No frames shifted in empty range" +); + +// Test 6: Multiple shifts applied sequentially +console.log("\n=== Test 6: Multiple shifts applied sequentially ==="); +const testData6 = createMockData(); + +const shifts = [ + { startTime: 98125, endTime: 157289, shift: 3750 }, + { startTime: 157289, endTime: 215928, shift: 7294 }, +]; + +const result6 = applyMultipleShifts(testData6, shifts); + +// Frame 1 was shifted by 3750 in first operation +assert( + result6.data.control.frame_1.start === 103750, + "First shift applied" +); +// Frame 4 was shifted by 7294 in second operation +assert( + result6.data.control.frame_4.start === 187294, + "Second shift applied" +); +assert( + result6.results.length === 2, + "Results recorded for 2 shifts" +); + +// Test 7: Overlapping shifts +console.log("\n=== Test 7: Overlapping shifts ==="); +const testData7 = createMockData(); + +const shifts7 = [ + { startTime: 98125, endTime: 200000, shift: 5000 }, + { startTime: 150000, endTime: 250000, shift: 3000 }, +]; + +const result7 = applyMultipleShifts(testData7, shifts7); + +// Frame 2 (start: 120000) +// First shift: 120000 + 5000 = 125000 +// Second shift: 125000 (not in 150000-250000 range, so no second shift) +assert( + result7.data.control.frame_2.start === 125000, + "Frame 2 shifted by first operation only" +); +// Frame 4 (start: 180000) +// First shift: 180000 + 5000 = 185000 +// Second shift: 185000 + 3000 = 188000 +assert( + result7.data.control.frame_4.start === 188000, + "Frame 4 shifted by both operations" +); + +// Test 8: Error handling - invalid range +console.log("\n=== Test 8: Error handling - invalid range ==="); +const testData8 = createMockData(); + +try { + shiftFramesInRange(testData8.control, 157289, 98125, 1000); + console.error("✗ FAIL: Should throw error for invalid range"); + process.exit(1); +} catch (error) { + assert( + error.message.includes("Invalid time range"), + "Throws error when startTime >= endTime" + ); +} + +// Test 9: Position frames also shifted +console.log("\n=== Test 9: Position frames also shifted ==="); +const testData9 = createMockData(); + +const shifts9 = [ + { startTime: 98125, endTime: 157289, shift: 3750 }, +]; + +const result9 = applyMultipleShifts(testData9, shifts9); + +assert( + result9.data.position.frame_1.start === 103750, + "Position frame 1 shifted" +); +assert( + result9.data.position.frame_2.start === 123750, + "Position frame 2 shifted" +); +assert( + result9.results[0].positionShifted === 2, + "Results show 2 position frames shifted" +); + +// Test 10: Original data not modified +console.log("\n=== Test 10: Original data not modified ==="); +const testData10 = createMockData(); +const originalStart = testData10.control.frame_1.start; + +const shifts10 = [ + { startTime: 98125, endTime: 157289, shift: 3750 }, +]; + +applyMultipleShifts(testData10, shifts10); + +assert( + testData10.control.frame_1.start === originalStart, + "Original data not modified" +); + +console.log("\n=== All tests passed! ===\n"); \ No newline at end of file From 54cf717d1f84511eda479bbc3e371617569ddf54 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Tue, 20 Jan 2026 14:58:30 +0800 Subject: [PATCH 09/12] SCRIPTS-#644 added TODO.md, also added some comments --- scripts/editing/TODO.md | 3 + scripts/editing/changeLEDLength.js | 114 +---------------------------- scripts/editing/copyPart.js | 2 + 3 files changed, 7 insertions(+), 112 deletions(-) create mode 100644 scripts/editing/TODO.md diff --git a/scripts/editing/TODO.md b/scripts/editing/TODO.md new file mode 100644 index 000000000..18a8bbda1 --- /dev/null +++ b/scripts/editing/TODO.md @@ -0,0 +1,3 @@ +- rewrite the code in rust +- auto backup database into JSON? +- use SQL instead of modifying JSON ( directly modify the database is faster ) \ No newline at end of file diff --git a/scripts/editing/changeLEDLength.js b/scripts/editing/changeLEDLength.js index 47ad8373d..8d2d1c339 100644 --- a/scripts/editing/changeLEDLength.js +++ b/scripts/editing/changeLEDLength.js @@ -1,6 +1,8 @@ /* * This script updates LED lengths across dancers and adjusts LED effects and status accordingly. * If a dancer model's LED length changes, all related LED effects and control data are updated. + * + * The modified data is saved to 'exportData_ledLength_updated.json'. */ const fs = require("fs"); @@ -141,115 +143,3 @@ if (require.main === module) { module.exports = { changeLEDLength, buildModelMap, updateLEDEffects, updateLEDStatus }; -// const oldData = require("./out/exportData.json"); -// const newData = require("../../utils/jsons/exportDataEmpty.json"); - -// const zip = (a, b) => a.map((k, i) => [k, b[i]]); - -// const main = (oldData, newData) => { -// const oldModelMap = oldData.dancer.reduce((acc, dancer) => { -// if (!(dancer.model in acc)) { -// acc[dancer.model] = dancer.parts; -// } -// return acc; -// }, {}); - -// const newModelMap = newData.dancer.reduce((acc, dancer) => { -// if (!(dancer.model in acc)) { -// acc[dancer.model] = dancer.parts; -// } -// return acc; -// }, {}); - -// Object.keys(newModelMap).forEach((modelName) => { -// if (!(modelName in oldModelMap)) { -// console.log(modelName, "not found in new data"); -// return; -// } - -// const oldDancerParts = oldModelMap[modelName]; -// const newDancerParts = newModelMap[modelName]; - -// zip(oldDancerParts, newDancerParts).forEach(([oldPart, newPart]) => { -// if (oldPart.type !== "LED") { -// return; -// } - -// if (oldPart.length !== newPart.length) { -// // console.log( -// // `LED length for ${modelName} ${oldPart.name} changed from ${oldPart.length} to ${newPart.length}` -// // ); - -// // LED Effects -// let modelPartEffects = oldData.LEDEffects[modelName][oldPart.name]; - -// if (oldPart.length < newPart.length) { -// // console.log("Old length is less than new length"); - -// Object.keys(modelPartEffects).forEach((effectName) => { -// let effect = modelPartEffects[effectName]; -// // console.log(effectName); -// const lastLED = -// effect.frames[0].LEDs[effect.frames[0].LEDs.length - 1]; -// effect.frames.forEach((frame) => { -// frame.LEDs = frame.LEDs.concat( -// Array(newPart.length - oldPart.length).fill(lastLED) -// ); -// }); -// }); -// } else { -// // console.log("Old length is greater than new length"); - -// Object.keys(modelPartEffects).forEach((effectName) => { -// let effect = modelPartEffects[effectName]; -// effect.frames.forEach((frame) => { -// frame.LEDs = frame.LEDs.slice(0, newPart.length); -// }); -// }); -// } - -// // LED bulb data -// let dancerIndices = oldData.dancer.reduce((indices, dancer, index) => { -// if (dancer.model === modelName) { -// indices.push(index); -// } -// return indices; -// }, []); -// dancerIndices.forEach((dancerIndex) => { -// let partIndex = oldData.dancer[dancerIndex].parts.findIndex( -// (part) => part.name === oldPart.name -// ); -// Object.values(oldData.control).forEach((controlFrame) => { -// oldLEDStatus = controlFrame.led_status[dancerIndex][partIndex]; -// if (oldLEDStatus.length !== 0) { -// if (oldPart.length < newPart.length) { -// controlFrame.led_status[dancerIndex][partIndex] = -// oldLEDStatus.concat( -// Array(newPart.length - oldPart.length).fill( -// oldLEDStatus[oldLEDStatus.length - 1] -// ) -// ); -// } else { -// controlFrame.led_status[dancerIndex][partIndex] = -// oldLEDStatus.slice(0, newPart.length); -// } -// } -// }); -// }); - -// oldPart.length = newPart.length; -// } -// }); -// }); - -// oldData.dancer.forEach((oldDancer) => { -// const modelName = oldDancer.model; -// oldDancer.parts = oldModelMap[modelName]; -// }); - -// return oldData; -// }; - -// const modifiedData = main(oldData, newData); - -// console.log(JSON.stringify(modifiedData, null, 2)); diff --git a/scripts/editing/copyPart.js b/scripts/editing/copyPart.js index 85d3dd29c..c4f41a7c3 100644 --- a/scripts/editing/copyPart.js +++ b/scripts/editing/copyPart.js @@ -1,6 +1,8 @@ /* * This script copies part status and LED status from one frame to all frames within a time range. * Useful for applying the same part configuration across multiple frames. + * + * The modified data is saved to 'props.json'. */ const fs = require("fs"); From 17741cd21c4fcb8a7d6037f2b6ec1852f8881a0b Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Mon, 2 Feb 2026 17:33:29 +0800 Subject: [PATCH 10/12] modified TODO.md --- scripts/editing/TODO.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/editing/TODO.md b/scripts/editing/TODO.md index 18a8bbda1..754250eed 100644 --- a/scripts/editing/TODO.md +++ b/scripts/editing/TODO.md @@ -1,3 +1,4 @@ - rewrite the code in rust - auto backup database into JSON? -- use SQL instead of modifying JSON ( directly modify the database is faster ) \ No newline at end of file +- use SQL instead of modifying JSON ( directly modify the database is faster ) +- auto backup prompt (y/n) \ No newline at end of file From 3426d101fd8d8e800710fb1241e67d9fd5ad9ff2 Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Tue, 10 Feb 2026 15:13:10 +0800 Subject: [PATCH 11/12] make changeLEDAlpha directly modify DB --- scripts/editing/changeLEDAlpha.js | 78 +++++------ scripts/editing/changeLEDAlpha.test.js | 181 ------------------------- 2 files changed, 34 insertions(+), 225 deletions(-) delete mode 100644 scripts/editing/changeLEDAlpha.test.js diff --git a/scripts/editing/changeLEDAlpha.js b/scripts/editing/changeLEDAlpha.js index ebd27770e..386e4615c 100644 --- a/scripts/editing/changeLEDAlpha.js +++ b/scripts/editing/changeLEDAlpha.js @@ -1,12 +1,11 @@ /* - * This script updates FIBER alpha (opacity) values across all frames. - * It changes FIBER parts with alpha value 200 to 227. - * - * The updated data is saved to `scripts/editing/fiber.json`. + * This script updates FIBER alpha (opacity) values in the database. + * It changes FIBER parts with alpha value 200 to 227 for the first 5 dancers. */ -const fs = require("fs"); -const path = require("path"); +const { PrismaClient } = require("@prisma/client"); + +const prisma = new PrismaClient(); // Configuration const DANCER_COUNT = 5; @@ -14,48 +13,39 @@ const TARGET_PART_TYPE = "FIBER"; const OLD_ALPHA = 200; const NEW_ALPHA = 227; -const updateFiberAlpha = (data) => { - for (const [frameKey, controlData] of Object.entries(data.control)) { - for (let dancer_id = 0; dancer_id < DANCER_COUNT; dancer_id++) { - if (!data.dancer[dancer_id]) continue; - - const parts = data.dancer[dancer_id].parts; - - for (let part_id = 0; part_id < parts.length; part_id++) { - const part = parts[part_id]; - - // Only process FIBER parts with target alpha value - if ( - part.type === TARGET_PART_TYPE && - controlData.status[dancer_id]?.[part_id]?.[1] === OLD_ALPHA - ) { - controlData.status[dancer_id][part_id][1] = NEW_ALPHA; - } - } - } - } -}; - -// Main execution -if (require.main === module) { +const updateFiberAlphaInDatabase = async () => { try { - const inputPath = path.join(__dirname, "../../../LightTableBackup/2025.03.24.json"); - - if (!fs.existsSync(inputPath)) { - throw new Error(`Input file not found: ${inputPath}`); - } - - const data = JSON.parse(fs.readFileSync(inputPath, "utf-8")); - - updateFiberAlpha(data); - - const outputPath = path.join(__dirname, "./fiber.json"); - fs.writeFileSync(outputPath, JSON.stringify(data, null, 2)); - console.log(`✓ Updated fiber alpha values saved to ${outputPath}`); + // Update all ControlData records where: + // - The part type is FIBER + // - The alpha value is OLD_ALPHA + // - The dancer_id is within the first DANCER_COUNT dancers + const result = await prisma.controlData.updateMany({ + where: { + alpha: OLD_ALPHA, + part: { + type: TARGET_PART_TYPE, + }, + dancer_id: { + in: Array.from({ length: DANCER_COUNT }, (_, i) => i + 1), + }, + }, + data: { + alpha: NEW_ALPHA, + }, + }); + + console.log(`✓ Updated ${result.count} records in database`); } catch (error) { console.error("Error:", error.message); process.exit(1); + } finally { + await prisma.$disconnect(); } +}; + +// Main execution +if (require.main === module) { + updateFiberAlphaInDatabase(); } -module.exports = { updateFiberAlpha }; \ No newline at end of file +module.exports = { updateFiberAlphaInDatabase }; \ No newline at end of file diff --git a/scripts/editing/changeLEDAlpha.test.js b/scripts/editing/changeLEDAlpha.test.js deleted file mode 100644 index 162139e2b..000000000 --- a/scripts/editing/changeLEDAlpha.test.js +++ /dev/null @@ -1,181 +0,0 @@ -const { updateFiberAlpha } = require("./changeLEDAlpha"); - -// Mock data for testing -const createMockData = () => ({ - dancer: [ - { - parts: [ - { type: "FIBER", name: "head" }, - { type: "FIBER", name: "arm" }, - { type: "LED", name: "hand" }, - { type: "FIBER", name: "leg" }, - ], - }, - { - parts: [ - { type: "FIBER", name: "head" }, - { type: "LED", name: "arm" }, - ], - }, - { - parts: [ - { type: "FIBER", name: "head" }, - ], - }, - { - parts: [ - { type: "FIBER", name: "torso" }, - ], - }, - { - parts: [ - { type: "FIBER", name: "head" }, - ], - }, - ], - control: { - frame_0: { - status: [ - // Dancer 0 - [ - [255, 200], // FIBER, alpha 200 → should change to 227 - [255, 200], // FIBER, alpha 200 → should change to 227 - [255, 200], // LED, alpha 200 → should NOT change - [255, 100], // FIBER, alpha 100 → should NOT change - ], - // Dancer 1 - [ - [255, 200], // FIBER, alpha 200 → should change to 227 - [255, 200], // LED, alpha 200 → should NOT change - ], - // Dancer 2 - [ - [255, 200], // FIBER, alpha 200 → should change to 227 - ], - // Dancer 3 - [ - [255, 200], // FIBER, alpha 200 → should change to 227 - ], - // Dancer 4 - [ - [255, 200], // FIBER, alpha 200 → should change to 227 - ], - ], - }, - frame_1: { - status: [ - // Dancer 0 - [ - [255, 200], // FIBER, alpha 200 → should change to 227 - [255, 150], // FIBER, alpha 150 → should NOT change - [255, 200], // LED, alpha 200 → should NOT change - [255, 200], // FIBER, alpha 200 → should change to 227 - ], - // Dancer 1 - [ - [255, 200], // FIBER, alpha 200 → should change to 227 - [255, 200], // LED, alpha 200 → should NOT change - ], - // Dancer 2-4 similar - [[255, 200]], - [[255, 200]], - [[255, 200]], - ], - }, - }, -}); - -// Test utilities -const assert = (condition, message) => { - if (!condition) { - console.error(`✗ FAIL: ${message}`); - process.exit(1); - } - console.log(`✓ PASS: ${message}`); -}; - -// Test 1: FIBER parts with alpha 200 are updated -console.log("\n=== Test 1: FIBER alpha 200 → 227 ==="); -const testData1 = createMockData(); -updateFiberAlpha(testData1); - -assert( - testData1.control.frame_0.status[0][0][1] === 227, - "Dancer 0, part 0 (FIBER): alpha 200 → 227" -); -assert( - testData1.control.frame_0.status[0][1][1] === 227, - "Dancer 0, part 1 (FIBER): alpha 200 → 227" -); -assert( - testData1.control.frame_0.status[1][0][1] === 227, - "Dancer 1, part 0 (FIBER): alpha 200 → 227" -); - -// Test 2: LED parts are NOT updated -console.log("\n=== Test 2: LED parts unchanged ==="); -const testData2 = createMockData(); -updateFiberAlpha(testData2); - -assert( - testData2.control.frame_0.status[0][2][1] === 200, - "Dancer 0, part 2 (LED): alpha 200 should remain unchanged" -); -assert( - testData2.control.frame_0.status[1][1][1] === 200, - "Dancer 1, part 1 (LED): alpha 200 should remain unchanged" -); - -// Test 3: FIBER parts with different alpha are NOT updated -console.log("\n=== Test 3: FIBER with different alpha unchanged ==="); -const testData3 = createMockData(); -updateFiberAlpha(testData3); - -assert( - testData3.control.frame_0.status[0][3][1] === 100, - "Dancer 0, part 3 (FIBER): alpha 100 should remain unchanged" -); -assert( - testData3.control.frame_1.status[0][1][1] === 150, - "Dancer 0, part 1 (FIBER): alpha 150 should remain unchanged" -); - -// Test 4: Multiple frames processed -console.log("\n=== Test 4: Multiple frames processed ==="); -const testData4 = createMockData(); -updateFiberAlpha(testData4); - -assert( - testData4.control.frame_1.status[0][0][1] === 227, - "Frame 1, Dancer 0, part 0 (FIBER): alpha 200 → 227" -); -assert( - testData4.control.frame_1.status[0][3][1] === 227, - "Frame 1, Dancer 0, part 3 (FIBER): alpha 200 → 227" -); - -// Test 5: All dancers processed -console.log("\n=== Test 5: All dancers processed ==="); -const testData5 = createMockData(); -updateFiberAlpha(testData5); - -for (let i = 0; i < 5; i++) { - assert( - testData5.control.frame_0.status[i][0][1] === 227, - `Dancer ${i}, part 0 (FIBER): alpha 200 → 227` - ); -} - -// Test 6: Color values unchanged -console.log("\n=== Test 6: Color values unchanged ==="); -const testData6 = createMockData(); -const beforeColor = testData6.control.frame_0.status[0][0][0]; -updateFiberAlpha(testData6); -const afterColor = testData6.control.frame_0.status[0][0][0]; - -assert( - beforeColor === afterColor, - "Color values should not be modified" -); - -console.log("\n=== All tests passed! ===\n"); \ No newline at end of file From 36159f5aba433f7c1ee5b10cb5ebfe6bb08f63ba Mon Sep 17 00:00:00 2001 From: EasonLee2006 Date: Tue, 10 Feb 2026 15:38:19 +0800 Subject: [PATCH 12/12] added comment --- scripts/editing/changeLEDAlpha.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/editing/changeLEDAlpha.js b/scripts/editing/changeLEDAlpha.js index 386e4615c..388395a9d 100644 --- a/scripts/editing/changeLEDAlpha.js +++ b/scripts/editing/changeLEDAlpha.js @@ -26,7 +26,7 @@ const updateFiberAlphaInDatabase = async () => { type: TARGET_PART_TYPE, }, dancer_id: { - in: Array.from({ length: DANCER_COUNT }, (_, i) => i + 1), + in: Array.from({ length: DANCER_COUNT }, (_, i) => i + 1), // [1, 2, 3, 4, 5] }, }, data: {