| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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 "", "" |
| |
|