UNPKG

kipm

Version:

KiCad component package manager

464 lines (400 loc) 17.3 kB
// easyeda_importer.js const OBJFile = require('obj-file-parser'); const { EasyedaApi } = require("./easyedaApi"); const { mat4, vec3, glMatrix, quat } = require("gl-matrix"); const { EeSymbol, EeSymbolInfo, EeSymbolBbox, EeSymbolPinSettings, EeSymbolPinDot, EeSymbolPinPath, EeSymbolPinName, EeSymbolPinDotBis, EeSymbolPinClock, EeSymbolPin, EeSymbolRectangle, EeSymbolPolyline, EeSymbolPolygon, EeSymbolPath, EeSymbolCircle, EeSymbolEllipse, EeSymbolArc, // Footprint models: EeFootprintInfo, EeFootprintBbox, EeFootprintPad, EeFootprintTrack, EeFootprintHole, EeFootprintVia, EeFootprintCircle, EeFootprintRectangle, EeFootprintArc, EeFootprintText, // 3D model classes: Ee3dModel, Ee3dModelBase, ee_footprint, } = require("./parametersEasyeda"); // --- Helper: zip two arrays into an object --- function zip(arr, values) { const result = {}; const len = Math.min(arr.length, values.length); for (let i = 0; i < len; i++) { result[arr[i]] = values[i]; } return result; } // -------------------- EasyEDA Symbol Handlers -------------------- function add_easyeda_pin(pin_data, ee_symbol) { const segments = pin_data.split("^^"); const ee_segments = segments.map((seg) => seg.split("~")); // For settings, skip the designator element (index 0) const settingsData = zip(EeSymbolPinSettings.fields, ee_segments[0].slice(1)); const pin_settings = new EeSymbolPinSettings(settingsData); const pin_dot = new EeSymbolPinDot({ dot_x: parseFloat(ee_segments[1][0]), dot_y: parseFloat(ee_segments[1][1]), }); const pin_path = new EeSymbolPinPath({ path: ee_segments[2][0], color: ee_segments[2][1], }); const pinNameData = zip(EeSymbolPinName.fields, ee_segments[3]); const pin_name = new EeSymbolPinName(pinNameData); const pin_dot_bis = new EeSymbolPinDotBis({ is_displayed: ee_segments[5][0], circle_x: parseFloat(ee_segments[5][1]), circle_y: parseFloat(ee_segments[5][2]), }); const pin_clock = new EeSymbolPinClock({ is_displayed: ee_segments[6][0], path: ee_segments[6][1], }); ee_symbol.pins.push( new EeSymbolPin({ settings: pin_settings, pin_dot: pin_dot, pin_path: pin_path, name: pin_name, dot: pin_dot_bis, clock: pin_clock, }) ); } function add_easyeda_rectangle(rectangle_data, ee_symbol) { const data = zip(EeSymbolRectangle.fields, rectangle_data.split("~").slice(1)); ee_symbol.rectangles.push(new EeSymbolRectangle(data)); } function add_easyeda_polyline(polyline_data, ee_symbol) { const data = zip( EeSymbolPolyline.fields, polyline_data.split("~").slice(1) ); ee_symbol.polylines.push(new EeSymbolPolyline(data)); } function add_easyeda_polygon(polygon_data, ee_symbol) { const data = zip(EeSymbolPolygon.fields, polygon_data.split("~").slice(1)); ee_symbol.polygons.push(new EeSymbolPolygon(data)); } function add_easyeda_path(path_data, ee_symbol) { const data = zip(EeSymbolPath.fields, path_data.split("~").slice(1)); ee_symbol.paths.push(new EeSymbolPath(data)); } function add_easyeda_circle(circle_data, ee_symbol) { const data = zip(EeSymbolCircle.fields, circle_data.split("~").slice(1)); ee_symbol.circles.push(new EeSymbolCircle(data)); } function add_easyeda_ellipse(ellipse_data, ee_symbol) { const data = zip(EeSymbolEllipse.fields, ellipse_data.split("~").slice(1)); ee_symbol.ellipses.push(new EeSymbolEllipse(data)); } function add_easyeda_arc(arc_data, ee_symbol) { const data = zip(EeSymbolArc.fields, arc_data.split("~").slice(1)); ee_symbol.arcs.push(new EeSymbolArc(data)); } const easyeda_handlers = { P: add_easyeda_pin, R: add_easyeda_rectangle, E: add_easyeda_ellipse, C: add_easyeda_circle, A: add_easyeda_arc, PL: add_easyeda_polyline, PG: add_easyeda_polygon, PT: add_easyeda_path, // "PI": ... (not supported) }; // -------------------- EasyEDA Symbol Importer -------------------- class EasyedaSymbolImporter { constructor(easyedaCpCadData) { this.input = easyedaCpCadData; this.output = this.extract_easyeda_data( easyedaCpCadData, easyedaCpCadData.dataStr.head.c_para ); } getSymbol() { return this.output; } extract_easyeda_data(ee_data, ee_data_info) { const new_ee_symbol = new EeSymbol({ info: { name: ee_data_info["name"], prefix: ee_data_info["pre"], package: ee_data_info["package"] || null, manufacturer: ee_data_info["BOM_Manufacturer"] || null, datasheet: ee_data.lcsc ? ee_data.lcsc.url : null, lcsc_id: ee_data.lcsc ? ee_data.lcsc.number : null, jlc_id: ee_data_info["BOM_JLCPCB Part Class"] || null, }, bbox: new EeSymbolBbox({ x: parseFloat(ee_data.dataStr.head.x), y: parseFloat(ee_data.dataStr.head.y), }), }); ee_data.dataStr.shape.forEach((line) => { const designator = line.split("~")[0]; if (easyeda_handlers.hasOwnProperty(designator)) { easyeda_handlers[designator](line, new_ee_symbol); } else { console.warn(`Unknown symbol designator: ${designator}`); } }); return new_ee_symbol; } } // -------------------- EasyEDA Footprint Importer -------------------- class EasyedaFootprintImporter { constructor(easyedaCpCadData) { this.input = easyedaCpCadData; } getFootprint() { return this.output; } async extract_easyeda_data(ee_data_str, ee_data_info, is_smd) { const new_ee_footprint = new ee_footprint({ info: { name: ee_data_info["package"], fp_type: is_smd ? "smd" : "tht", model_3d_name: ee_data_info["3DModel"] || null, }, bbox: new EeFootprintBbox({ x: parseFloat(ee_data_str.head.x), y: parseFloat(ee_data_str.head.y), }), model_3d: null, }); ee_data_str.shape.forEach((line) => { const ee_designator = line.split("~")[0]; const ee_fields = line.split("~").slice(1); if (ee_designator === "PAD") { const data = zip(EeFootprintPad.fields, ee_fields.slice(0, 18)); new_ee_footprint.pads.push(new EeFootprintPad(data)); } else if (ee_designator === "TRACK") { const data = zip(EeFootprintTrack.fields, ee_fields); new_ee_footprint.tracks.push(new EeFootprintTrack(data)); } else if (ee_designator === "HOLE") { const data = zip(EeFootprintHole.fields, ee_fields); new_ee_footprint.holes.push(new EeFootprintHole(data)); } else if (ee_designator === "VIA") { const data = zip(EeFootprintVia.fields, ee_fields); new_ee_footprint.vias.push(new EeFootprintVia(data)); } else if (ee_designator === "CIRCLE") { const data = zip(EeFootprintCircle.fields, ee_fields); new_ee_footprint.circles.push(new EeFootprintCircle(data)); } else if (ee_designator === "ARC") { const data = zip(EeFootprintArc.fields, ee_fields); new_ee_footprint.arcs.push(new EeFootprintArc(data)); } else if (ee_designator === "RECT") { const data = zip(EeFootprintRectangle.fields, ee_fields); new_ee_footprint.rectangles.push(new EeFootprintRectangle(data)); } else if (ee_designator === "TEXT") { const data = zip(EeFootprintText.fields, ee_fields); new_ee_footprint.texts.push(new EeFootprintText(data)); } else if (ee_designator === "SVGNODE") { /* Is this needed? 3d model are downloaded separately... new_ee_footprint.model_3d = new Easyeda3dModelImporter( { packageDetail: { dataStr: { shape: [line] } } }, false ); const a = 1; */ } else if (ee_designator === "SOLIDREGION") { // Not implemented } else { console.warn(`Unknown footprint designator: ${ee_designator}`); } }); /* removing this for now. footprint shouldnt need its own copy of the 3d model, injecting main 3d model in main thread instead if (new_ee_footprint.model_3d) { new_ee_footprint.model_3d = await new_ee_footprint.model_3d.create_3d_model(); } */ return new_ee_footprint; } } // -------------------- EasyEDA 3D Model Importer -------------------- class Easyeda3dModelImporter { constructor(lcscComponent) { this.lcscComponent = lcscComponent; this.downloadRaw3dModel = true; } async create_3d_model() { const model_3d_info = this.lcscComponent.get3DModelInfo(); if (model_3d_info) { const model_3d = this.parse_3d_model_info(model_3d_info); if (this.downloadRaw3dModel) { model_3d.raw_obj = this.lcscComponent.get3dRawObj(); model_3d.step = this.lcscComponent.get3dStep(); let transformMatrix = mat4.create(); // Identity matrix const o = { "id": "d7bbe008f7644750b0331176fa9be5bf", "originX": 4196, "originY": -3147.5, "originRotation": 0, "x": -4.527699999999641, "y": 0, "z": 0, "rx": 0, "ry": 0, "rz": 90, "width": 40.1574, "height": 56.017797413, "isTop": true }; const options = { "id": model_3d_info.uuid, //"originX": 4108.5, # 3D model location on easyeda PCB - ignore //"originY": -3087.5, # 3D model location on easyeda PCB - ignore //"originRotation": 0, # 3D model location on easyeda PCB - ignore //"x": 0, //"y": 0, "z": model_3d_info.z, "rx": model_3d_info.c_rotation.split(',')[0], "ry": model_3d_info.c_rotation.split(',')[1], "rz": model_3d_info.c_rotation.split(',')[2], "width": model_3d_info.c_width, "height": model_3d_info.c_height, //"isTop": true # 3D model location on easyeda PCB - ignore } const objFile = new OBJFile(model_3d.raw_obj); const output = objFile.parse(); // Calculate bbox from parsed vertices const vertices = output.models[0].vertices; const boundingBox = vertices.reduce((acc, vertex) => ({ maxX: Math.max(acc.maxX, vertex.x), maxY: Math.max(acc.maxY, vertex.y), maxZ: Math.max(acc.maxZ, vertex.z), minX: Math.min(acc.minX, vertex.x), minY: Math.min(acc.minY, vertex.y), minZ: Math.min(acc.minZ, vertex.z) }), { maxX: -Infinity, maxY: -Infinity, maxZ: -Infinity, minX: Infinity, minY: Infinity, minZ: Infinity }); model_3d.boundingBox = boundingBox; const centerX = (boundingBox.maxX + boundingBox.minX) / 2 , centerY = (boundingBox.maxY + boundingBox.minY) / 2 , bottomZ = boundingBox.minZ; const translationMatrix = mat4.create(); mat4.translate(translationMatrix, translationMatrix, vec3.fromValues(-centerX, -centerY, -bottomZ)); // easyeda: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -7.1030225, 0.014705, 1] // kipm: 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -7.1030225, 0.014705, 1 const rotationZ = glMatrix.toRadian(options.rz); const rotationX = glMatrix.toRadian(options.rx); const rotationY = glMatrix.toRadian(options.ry); const rotationMatrix = mat4.create(); mat4.rotateZ(rotationMatrix, rotationMatrix, rotationZ); mat4.rotateX(rotationMatrix, rotationMatrix, rotationX); mat4.rotateY(rotationMatrix, rotationMatrix, rotationY); mat4.multiply(transformMatrix, rotationMatrix, translationMatrix); // easyeda: [6.1e-17, 1, 0, 0, -1, 6.1e-17, 0, 0, 0, 0, 1, 0, 7.1030225, -4.3493e-16, 0.014705, 1] // kipm: 6.1e-17, 1, 0, 0, -1, 6.1e-17, 0, 0, 0, 0, 1, 0, 7.1030225, -4.3493e-16, 0.014705, 1 const scaleX = options.width / (boundingBox.maxX - boundingBox.minX); const scaleY = options.height / (boundingBox.maxY - boundingBox.minY); const uniformScale = Math.min(scaleX, scaleY); mat4.scale(transformMatrix, transformMatrix, vec3.fromValues(uniformScale, uniformScale, uniformScale)); //mat4.translate(transformMatrix, transformMatrix, vec3.fromValues(options.x, options.y, options.z)); const translation = vec3.create(); const rotation = quat.create(); const scale = vec3.create(); mat4.getTranslation(translation, transformMatrix); mat4.getRotation(rotation, transformMatrix); mat4.getScaling(scale, transformMatrix); function quatToEuler(q) { // Assuming ZXY rotation order based on your original code // q = [x, y, z, w] const x = q[0], y = q[1], z = q[2], w = q[3]; // ZXY rotation order const rotX = Math.atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y)) * 180 / Math.PI; const rotY = Math.asin(Math.max(-1, Math.min(1, 2 * (w * y - z * x)))) * 180 / Math.PI; const rotZ = Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)) * 180 / Math.PI; // Normalize to 0-360 return { x: ((rotX % 360) + 360) % 360, y: ((rotY % 360) + 360) % 360, z: ((rotZ % 360) + 360) % 360 }; } const rotationDegrees = quatToEuler(rotation); rotationDegrees.x = ((rotationDegrees.x % 360) + 360) % 360; rotationDegrees.y = ((rotationDegrees.y % 360) + 360) % 360; rotationDegrees.z = ((rotationDegrees.z % 360) + 360) % 360; // console.log("Translation (x, y, z):"); // console.log(`x: ${translation[0].toFixed(2)}, y: ${translation[1].toFixed(2)}, z: ${translation[2].toFixed(2)}`); // console.log("Rotation (degrees, x, y, z):"); // console.log(`x: ${rotationDegrees.x.toFixed(2)}, y: ${rotationDegrees.y.toFixed(2)}, z: ${rotationDegrees.z.toFixed(2)}`); this.lcscComponent.translation.x += +translation[0].toFixed(2); this.lcscComponent.translation.y += +translation[1].toFixed(2); this.lcscComponent.translation.z += +translation[2].toFixed(2); } return model_3d; } console.warn("No 3D model available for this component"); return null; } get_3d_model_info(ee_data) { for (const line of ee_data) { const ee_designator = line.split("~")[0]; if (ee_designator === "SVGNODE") { const raw_json = line.split("~").slice(1)[0]; return JSON.parse(raw_json).attrs; } } return {}; } parse_3d_model_info(info) { return new Ee3dModel({ name: info.title, uuid: info.uuid, translation: new Ee3dModelBase({ x: info.c_origin.split(",")[0], y: info.c_origin.split(",")[1], z: info.z, }), rotation: new Ee3dModelBase( zip(Ee3dModelBase.fields, info.c_rotation.split(",")) ), }); } } module.exports = { EasyedaSymbolImporter, EasyedaFootprintImporter, Easyeda3dModelImporter, add_easyeda_pin, add_easyeda_rectangle, add_easyeda_polyline, add_easyeda_polygon, add_easyeda_path, add_easyeda_circle, add_easyeda_ellipse, add_easyeda_arc, };