| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| from string import Template |
| import FreeCAD |
| import Path.Log |
| import base64 |
| import os |
|
|
| from Path.Main.Sanity.HTMLTemplate import ( |
| html_template, |
| base_template, |
| squawk_template, |
| tool_template, |
| op_run_template, |
| op_tool_template, |
| tool_item_template, |
| ) |
|
|
| translate = FreeCAD.Qt.translate |
|
|
| if False: |
| Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| Path.Log.trackModule(Path.Log.thisModule()) |
| else: |
| Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
|
|
|
|
| class ReportGenerator: |
| def bytes_to_base64_with_tag(self, image_bytes, mime_type="image/png", alt="Image"): |
| """ |
| Takes image bytes and returns (base64_string, <img> tag) for embedding in HTML. |
| Default mime_type is image/png. |
| """ |
|
|
| if not image_bytes: |
| return "", "" |
| encoded_string = base64.b64encode(image_bytes).decode() |
| html_tag = f'<img src="data:{mime_type};base64,{encoded_string}" alt="{alt}" />' |
| return encoded_string, html_tag |
|
|
| def __init__(self, data, embed_images=False): |
| self.embed_images = embed_images |
| self.squawks = "" |
| self.tools = "" |
| self.run_summary_ops = "" |
| self.formatted_data = {} |
| self.translated_labels = { |
| "dateLabel": translate("CAM_Sanity", "Date"), |
| "datumLabel": translate("CAM_Sanity", "Part Datum"), |
| "descriptionLabel": translate("CAM_Sanity", "Description"), |
| "diameterLabel": translate("CAM_Sanity", "Tool Diameter"), |
| "feedLabel": translate("CAM_Sanity", "Feed Rate"), |
| "fileSizeLabel": translate("CAM_Sanity", "File Size (kB)"), |
| "fixturesLabel": translate("CAM_Sanity", "Fixtures"), |
| "flagsLabel": translate("CAM_Sanity", "Post Processor Flags"), |
| "gcodeFileLabel": translate("CAM_Sanity", "G-code File"), |
| "headingLabel": translate("CAM_Sanity", "Setup Report for CAM Job"), |
| "inspectionNotesLabel": translate("CAM_Sanity", "Inspection Notes"), |
| "lastpostLabel": translate("CAM_Sanity", "Last Post Process Date"), |
| "lineCountLabel": translate("CAM_Sanity", "Line Count"), |
| "machineLabel": translate("CAM_Sanity", "Machine"), |
| "manufLabel": translate("CAM_Sanity", "Manufacturer"), |
| "materialLabel": translate("CAM_Sanity", "Material"), |
| "noteLabel": translate("CAM_Sanity", "Note"), |
| "offsetsLabel": translate("CAM_Sanity", "Work Offsets"), |
| "opLabel": translate("CAM_Sanity", "Operation"), |
| "operatorLabel": translate("CAM_Sanity", "Operator"), |
| "orderByLabel": translate("CAM_Sanity", "Order By"), |
| "outputLabel": translate("CAM_Sanity", "Output (G-code)"), |
| "partInformationLabel": translate("CAM_Sanity", "Part Information"), |
| "partNumberLabel": translate("CAM_Sanity", "Part Number"), |
| "postLabel": translate("CAM_Sanity", "Postprocessor"), |
| "programmerLabel": translate("CAM_Sanity", "Programmer"), |
| "roughStockLabel": translate("CAM_Sanity", "Rough Stock"), |
| "runSummaryLabel": translate("CAM_Sanity", "Run Summary"), |
| "shapeLabel": translate("CAM_Sanity", "Tool Shape"), |
| "speedLabel": translate("CAM_Sanity", "Spindle Speed"), |
| "squawksLabel": translate("CAM_Sanity", "Squawks"), |
| "stopsLabel": translate("CAM_Sanity", "Stops"), |
| "sSpeedCarbideLabel": translate("CAM_Sanity", "Surface Speed Carbide"), |
| "sSpeedHSSLabel": translate("CAM_Sanity", "Surface Speed HSS"), |
| "tableOfContentsLabel": translate("CAM_Sanity", "Table of Contents"), |
| "tcLabel": translate("CAM_Sanity", "Tool Controller"), |
| "toolDataLabel": translate("CAM_Sanity", "Tool Data"), |
| "toolNumberLabel": translate("CAM_Sanity", "Tool Number"), |
| "urlLabel": translate("CAM_Sanity", "URL"), |
| "xDimLabel": translate("CAM_Sanity", "X Size"), |
| "yDimLabel": translate("CAM_Sanity", "Y Size"), |
| "zDimLabel": translate("CAM_Sanity", "Z Size"), |
| "jobMinZLabel": translate("CAM_Sanity", "Minimum Z"), |
| "jobMaxZLabel": translate("CAM_Sanity", "Maximum Z"), |
| "coolantLabel": translate("CAM_Sanity", "Coolant Mode"), |
| "cycleTimeLabel": translate("CAM_Sanity", "Cycle Time"), |
| "PartLabel": translate("CAM_Sanity", "Part"), |
| "SequenceLabel": translate("CAM_Sanity", "Sequence"), |
| "JobTypeLabel": translate("CAM_Sanity", "Job Type"), |
| "CADLabel": translate("CAM_Sanity", "CAD File"), |
| "LastSaveLabel": translate("CAM_Sanity", "Last Save"), |
| "CustomerLabel": translate("CAM_Sanity", "Customer"), |
| } |
|
|
| |
| for block in [ |
| "baseData", |
| "designData", |
| "runData", |
| "outputData", |
| "fixtureData", |
| "stockData", |
| ]: |
| for key, val in data[block].items(): |
| Path.Log.debug(f"key: {key} val: {val}") |
| if key == "squawkData": |
| self._format_squawks(val) |
| elif key == "bases": |
| self._format_bases(val) |
| elif key == "operations": |
| self._format_run_summary_ops(val) |
| elif key in ["baseimage", "imagepath", "datumImage", "stockImage"]: |
| Path.Log.debug(f"key: {key} val: {val}") |
| if self.embed_images: |
| Path.Log.debug("Embedding images") |
| if isinstance(val, bytes): |
| encoded_image, tag = self.bytes_to_base64_with_tag( |
| val, mime_type="image/png", alt=key |
| ) |
| else: |
| encoded_image, tag = self.file_to_base64_with_tag(val) |
| else: |
| Path.Log.debug("Not Embedding images") |
| tag = f"<img src={val} name='Image' alt={key} />" |
| self.formatted_data[key] = tag |
| else: |
| self.formatted_data[key] = val |
|
|
| |
| for key, val in data["toolData"].items(): |
| if key == "squawkData": |
| self._format_squawks(val) |
| else: |
| toolNumber = key |
| toolAttributes = val |
| |
| if ( |
| self.embed_images |
| and "imagebytes" in toolAttributes |
| and toolAttributes["imagebytes"] |
| ): |
| _, tag = self.bytes_to_base64_with_tag( |
| toolAttributes["imagebytes"], mime_type="image/png", alt=key |
| ) |
| toolAttributes["imagepath"] = tag |
| elif "imagepath" in toolAttributes and toolAttributes["imagepath"] != "": |
| if self.embed_images: |
| encoded_image, tag = self.file_to_base64_with_tag( |
| toolAttributes["imagepath"] |
| ) |
| else: |
| tag = f"<img src={toolAttributes['imagepath']} name='Image' alt={key} />" |
| toolAttributes["imagepath"] = tag |
|
|
| self._format_tool(key, val) |
|
|
| self.formatted_data["squawks"] = self.squawks |
| self.formatted_data["run_summary_ops"] = self.run_summary_ops |
| self.formatted_data["tool_data"] = self.tools |
| self.formatted_data["tool_list"] = self._format_tool_list(data["toolData"]) |
|
|
| |
|
|
| def _format_tool_list(self, tool_data): |
| tool_list = "" |
| for key, val in tool_data.items(): |
| if key == "squawkData": |
| continue |
| else: |
| val["toolNumber"] = key |
| tool_list += tool_item_template.substitute(val) |
| return tool_list |
|
|
| def _format_run_summary_ops(self, op_data): |
| for op in op_data: |
| self.run_summary_ops += op_run_template.substitute(op) |
|
|
| def _format_tool(self, tool_number, tool_data): |
| td = {} |
| td["toolNumber"] = tool_number |
|
|
| for key, val in tool_data.items(): |
| if key == "squawkData": |
| self._format_squawks(val) |
| if key == "ops": |
| opslist = "" |
| for op in val: |
| op.update(self.translated_labels) |
| opslist += op_tool_template.substitute(op) |
| td[key] = opslist |
| else: |
| td[key] = val |
|
|
| td.update(self.translated_labels) |
| Path.Log.debug(f"Tool data: {td}") |
|
|
| self.tools += tool_template.substitute(td) |
|
|
| def _format_bases(self, base_data): |
| bases = "" |
| for key, val in base_data.items(): |
| bases += base_template.substitute({"key": key, "val": val}) |
| self.formatted_data["bases"] = bases |
|
|
| def _format_squawks(self, squawk_data): |
| for squawk in squawk_data: |
| if self.embed_images: |
| data, tag = self.file_to_base64_with_tag(squawk["squawkIcon"]) |
| else: |
| tag = f"<img src={squawk['squawkIcon']} name='Image' alt='TIP' />" |
|
|
| squawk["squawkIcon"] = tag |
| self.squawks += squawk_template.substitute(squawk) |
|
|
| def generate_html(self): |
| self.formatted_data.update(self.translated_labels) |
| html_content = html_template.substitute(self.formatted_data) |
| return html_content |
|
|
| def encode_gcode_to_base64(filepath): |
| with open(filepath, "rb") as file: |
| encoded_string = base64.b64encode(file.read()).decode("utf-8") |
| return encoded_string |
|
|
| def file_to_base64_with_tag(self, file_path): |
| |
| mime_types = { |
| ".jpg": "image/jpeg", |
| ".jpeg": "image/jpeg", |
| ".png": "image/png", |
| ".svg": "image/svg+xml", |
| ".gcode": "application/octet-stream", |
| } |
| extension = os.path.splitext(file_path)[1] |
| mime_type = mime_types.get( |
| extension, "application/octet-stream" |
| ) |
|
|
| if not os.path.exists(file_path): |
| Path.Log.error(f"File not found: {file_path}") |
| return "", "" |
|
|
| try: |
| |
| with open(file_path, "rb") as file: |
| encoded_string = base64.b64encode(file.read()).decode() |
|
|
| |
| if extension in [".jpg", ".jpeg", ".png", ".svg"]: |
| html_tag = f'<img src="data:{mime_type};base64,{encoded_string}">' |
| elif extension in [".gcode", ".nc", ".tap", ".cnc"]: |
| html_tag = f'<a href="data:{mime_type};base64,{encoded_string}" download="{os.path.basename(file_path)}">Download G-code File</a>' |
| else: |
| html_tag = f'<a href="data:{mime_type};base64,{encoded_string}" download="{os.path.basename(file_path)}">Download File</a>' |
|
|
| return encoded_string, html_tag |
| except FileNotFoundError: |
| Path.Log.error(f"File not found: {file_path}") |
| return "", "" |
|
|