优化了ai个零件库

This commit is contained in:
Robofish 2025-07-25 16:39:32 +08:00
parent 9fc6b4577a
commit 47e0b8419f
17 changed files with 480 additions and 102 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -8,7 +8,6 @@ from PyQt5.QtWidgets import QApplication
from app.main_window import MainWindow from app.main_window import MainWindow
# 启用 DPI 缩放 # 启用 DPI 缩放
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # 启用高 DPI 缩放 QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # 启用高 DPI 缩放

21
ai.py Normal file
View File

@ -0,0 +1,21 @@
import requests
import json
url = "http://154.37.215.220:11434/api/generate"
payload = {
"model": "qwen3:0.6b",
"prompt": "你好,介绍一下你自己"
}
response = requests.post(url, json=payload, stream=True)
for line in response.iter_lines():
if line:
try:
data = json.loads(line.decode('utf-8'))
# 只输出 response 字段内容
print(data.get("response", ""), end="", flush=True)
# 如果 done 为 True则换行
if data.get("done", False):
print()
except Exception as e:
pass # 忽略解析异常

View File

@ -26,20 +26,29 @@ class AboutInterface(QWidget):
layout.addWidget(card) layout.addWidget(card)
def on_check_update_clicked(self): def on_check_update_clicked(self):
latest = check_update(__version__) try:
if latest: latest = check_update(__version__)
InfoBar.success( if latest:
title="发现新版本", InfoBar.success(
content=f"检测到新版本:{latest},请前往官网或仓库下载更新。", title="发现新版本",
content=f"检测到新版本:{latest},请前往官网或仓库下载更新。",
parent=self,
position=InfoBarPosition.TOP,
duration=5000
)
elif latest is None:
InfoBar.info(
title="已是最新版本",
content="当前已是最新版本,无需更新。",
parent=self,
position=InfoBarPosition.TOP,
duration=3000
)
except Exception:
InfoBar.error(
title="检查更新失败",
content="无法获取最新版本,请检查网络连接。",
parent=self, parent=self,
position=InfoBarPosition.TOP, position=InfoBarPosition.TOP,
duration=5000 duration=4000
)
else:
InfoBar.info(
title="已是最新版本",
content="当前已是最新版本,无需更新。",
parent=self,
position=InfoBarPosition.TOP,
duration=3000
) )

182
app/ai_interface.py Normal file
View File

@ -0,0 +1,182 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from qfluentwidgets import TextEdit, LineEdit, PushButton, TitleLabel, SubtitleLabel, FluentIcon, InfoBar, InfoBarPosition
import requests
import json
class AIWorker(QThread):
response_signal = pyqtSignal(str)
done_signal = pyqtSignal()
error_signal = pyqtSignal(str) # 新增
def __init__(self, prompt, parent=None):
super().__init__(parent)
self.prompt = prompt
def run(self):
url = "http://154.37.215.220:11434/api/generate"
payload = {
"model": "qwen3:0.6b",
"prompt": self.prompt
}
try:
response = requests.post(url, json=payload, stream=True, timeout=60)
got_response = False
for line in response.iter_lines():
if line:
got_response = True
try:
data = json.loads(line.decode('utf-8'))
self.response_signal.emit(data.get("response", ""))
if data.get("done", False):
self.done_signal.emit()
break
except Exception:
continue
if not got_response:
self.error_signal.emit("服务器繁忙,请稍后再试。")
self.done_signal.emit()
except requests.ConnectionError:
self.error_signal.emit("网络连接失败,请检查网络设置。")
self.done_signal.emit()
except Exception as e:
self.error_signal.emit(f"[错误]: {str(e)}")
self.done_signal.emit()
class AIInterface(QWidget):
MAX_HISTORY = 20 # 新增最大对话条数
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("aiPage")
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(20, 20, 20, 20)
self.layout.setSpacing(10)
self.title = SubtitleLabel("MRobot AI小助手", self)
self.title.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.title)
self.chat_display = TextEdit(self)
self.chat_display.setReadOnly(True)
self.layout.addWidget(self.chat_display, stretch=1)
input_layout = QHBoxLayout()
self.input_box = LineEdit(self)
self.input_box.setPlaceholderText("请输入你的问题...")
input_layout.addWidget(self.input_box, stretch=1)
# self.send_btn = PushButton("发送", self)
self.send_btn = PushButton("发送", icon=FluentIcon.SEND, parent=self)
self.send_btn.setFixedWidth(80)
input_layout.addWidget(self.send_btn)
self.layout.addLayout(input_layout)
self.send_btn.clicked.connect(self.send_message)
self.input_box.returnPressed.connect(self.send_message)
self.worker = None
self.is_waiting = False
self.history = []
self.chat_display.setText(
"<b>MRobot:</b> 欢迎使用MRobot AI小助手!"
)
def send_message(self):
if self.is_waiting:
return
prompt = self.input_box.text().strip()
if not prompt:
return
if len(prompt) > 1000:
InfoBar.warning(
title='警告',
content="每条发送内容不能超过1000字请精简后再发送。",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM,
duration=-1,
parent=self
)
return
if len(self.history) >= self.MAX_HISTORY:
InfoBar.warning(
title='警告',
content="对话条数已达上限,请清理历史或重新开始。",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM,
duration=-1,
parent=self
)
return
self.append_chat("", prompt)
self.input_box.clear()
self.append_chat("MRobot", "", new_line=False)
self.is_waiting = True
# 只在首次对话时加入身份提示
if not self.history:
system_prompt = (
"你是MRobot是QUT青岛理工大学机器人战队的AI机器人。"
"请以此身份与用户进行交流。"
)
else:
system_prompt = ""
self.history.append({"role": "user", "content": prompt})
context = system_prompt + "\n" if system_prompt else ""
for msg in self.history:
if msg["role"] == "user":
context += f"你: {msg['content']}\n"
else:
context += f"AI: {msg['content']}\n"
self.worker = AIWorker(context)
self.worker.response_signal.connect(self.stream_response)
self.worker.done_signal.connect(self.finish_response)
self.worker.error_signal.connect(self.show_error) # 新增
self.worker.start()
def append_chat(self, sender, message, new_line=True):
if new_line:
self.chat_display.append(f"<b>{sender}:</b> {message}")
else:
self.chat_display.append(f"<b>{sender}:</b> ")
self.chat_display.moveCursor(self.chat_display.textCursor().End)
# 新增保存AI回复到历史
if sender == "AI" and message:
self.history.append({"role": "ai", "content": message})
def stream_response(self, text):
cursor = self.chat_display.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(text)
self.chat_display.setTextCursor(cursor)
# 新增流式保存AI回复
if self.history and self.history[-1]["role"] == "ai":
self.history[-1]["content"] += text
elif text:
self.history.append({"role": "ai", "content": text})
def finish_response(self):
self.chat_display.append("") # 换行
self.is_waiting = False
def show_error(self, msg): # 新增
InfoBar.error(
title='失败',
content=msg,
orient=Qt.Vertical,
isClosable=True,
position=InfoBarPosition.TOP,
duration=-1,
parent=self
)
self.is_waiting = False

View File

@ -3,6 +3,7 @@ from PyQt5.QtCore import Qt
from qfluentwidgets import PushSettingCard, FluentIcon, TabBar from qfluentwidgets import PushSettingCard, FluentIcon, TabBar
from .function_fit_interface import FunctionFitInterface from .function_fit_interface import FunctionFitInterface
from .ai_interface import AIInterface
class MiniToolInterface(QWidget): class MiniToolInterface(QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
@ -26,14 +27,22 @@ class MiniToolInterface(QWidget):
mainLayout = QVBoxLayout(self.mainPage) mainLayout = QVBoxLayout(self.mainPage)
mainLayout.setAlignment(Qt.AlignTop) # 卡片靠顶部 mainLayout.setAlignment(Qt.AlignTop) # 卡片靠顶部
self.card = PushSettingCard( self.card = PushSettingCard(
text="启动", text=" 启动",
icon=FluentIcon.UNIT, icon=FluentIcon.UNIT,
title="曲线拟合工具", title="曲线拟合工具",
content="简单的曲线拟合工具,支持多种函数类型", content="简单的曲线拟合工具,支持多种函数类型",
) )
mainLayout.addWidget(self.card) mainLayout.addWidget(self.card)
self.mainPage.setLayout(mainLayout)
self.mainPage.setLayout(mainLayout)
self.aiCard = PushSettingCard(
text="▶ 启动",
icon=FluentIcon.ROBOT,
title="MRobot AI助手",
content="与 MRobot 进行图一乐交流, 使用开源模型qwen3:0.6b。",
)
mainLayout.addWidget(self.aiCard)
self.aiCard.clicked.connect(self.open_ai_tab)
# 添加主页面到堆叠窗口 # 添加主页面到堆叠窗口
self.addSubInterface(self.mainPage, "mainPage", "工具箱主页") self.addSubInterface(self.mainPage, "mainPage", "工具箱主页")
@ -79,4 +88,17 @@ class MiniToolInterface(QWidget):
fit_page = FunctionFitInterface(self) fit_page = FunctionFitInterface(self)
self.addSubInterface(fit_page, "fitPage", "曲线拟合") self.addSubInterface(fit_page, "fitPage", "曲线拟合")
self.stackedWidget.setCurrentWidget(fit_page) self.stackedWidget.setCurrentWidget(fit_page)
self.tabBar.setCurrentTab("fitPage") self.tabBar.setCurrentTab("fitPage")
def open_ai_tab(self):
# 检查是否已存在标签页,避免重复添加
for i in range(self.stackedWidget.count()):
widget = self.stackedWidget.widget(i)
if widget.objectName() == "aiPage":
self.stackedWidget.setCurrentWidget(widget)
self.tabBar.setCurrentTab("aiPage")
return
ai_page = AIInterface(self)
self.addSubInterface(ai_page, "aiPage", "AI问答")
self.stackedWidget.setCurrentWidget(ai_page)
self.tabBar.setCurrentTab("aiPage")

View File

@ -1,51 +1,12 @@
from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget
from qfluentwidgets import SubtitleLabel, BodyLabel, HorizontalSeparator, PushButton, TreeWidget, ProgressBar, Dialog, InfoBar, InfoBarPosition, FluentIcon from qfluentwidgets import SubtitleLabel, BodyLabel, HorizontalSeparator, PushButton, TreeWidget, ProgressBar, Dialog, InfoBar, InfoBarPosition, FluentIcon, ProgressRing, Dialog
import requests import requests
import shutil import shutil
import os import os
from .tools.part_download import DownloadThread # 新增导入
from urllib.parse import quote from urllib.parse import quote
class DownloadThread(QThread):
progressChanged = pyqtSignal(int)
finished = pyqtSignal(list, list) # success, fail
def __init__(self, files, server_url, secret_key, local_dir, parent=None):
super().__init__(parent)
self.files = files
self.server_url = server_url
self.secret_key = secret_key
self.local_dir = local_dir
def run(self):
success, fail = [], []
total = len(self.files)
max_retry = 3
for idx, rel_path in enumerate(self.files):
retry = 0
while retry < max_retry:
try:
rel_path_unix = rel_path.replace("\\", "/")
encoded_path = quote(rel_path_unix)
url = f"{self.server_url}/download/{encoded_path}"
params = {"key": self.secret_key}
resp = requests.get(url, params=params, stream=True, timeout=10)
if resp.status_code == 200:
local_path = os.path.join(self.local_dir, rel_path)
os.makedirs(os.path.dirname(local_path), exist_ok=True)
with open(local_path, "wb") as f:
shutil.copyfileobj(resp.raw, f)
success.append(rel_path)
break
else:
retry += 1
except Exception:
retry += 1
else:
fail.append(rel_path)
self.progressChanged.emit(int((idx + 1) / total * 100))
self.finished.emit(success, fail)
class PartLibraryInterface(QWidget): class PartLibraryInterface(QWidget):
SERVER_URL = "http://154.37.215.220:5000" SERVER_URL = "http://154.37.215.220:5000"
SECRET_KEY = "MRobot_Download" SECRET_KEY = "MRobot_Download"
@ -151,41 +112,64 @@ class PartLibraryInterface(QWidget):
def download_selected_files(self): def download_selected_files(self):
files = self.get_checked_files() files = self.get_checked_files()
if not files: if not files:
InfoBar.info( dialog = Dialog(
title="提示", title="温馨提示",
content="请先勾选要下载的文件。", content="请先勾选需要下载的文件。",
parent=self, parent=self
position=InfoBarPosition.TOP,
duration=2000
) )
dialog.yesButton.setText("知道啦")
dialog.cancelButton.hide()
dialog.exec()
return return
self.progress_dialog = Dialog( # 创建进度环
title="正在下载", self.progress_ring = ProgressRing()
content="正在下载选中文件,请稍候...", self.progress_ring.setRange(0, 100)
parent=self self.progress_ring.setValue(0)
) self.progress_ring.setTextVisible(True)
self.progress_bar = ProgressBar() self.progress_ring.setFixedSize(32, 32)
self.progress_bar.setValue(0) self.progress_ring.setStrokeWidth(4)
self.progress_dialog.textLayout.addWidget(self.progress_bar)
self.progress_dialog.show()
# 展示消息条(关闭按钮即中断下载)
self.info_bar = InfoBar(
icon=FluentIcon.DOWNLOAD,
title="正在下载",
content="正在下载选中文件...",
parent=self,
position=InfoBarPosition.TOP,
duration=-1 # 不自动消失
)
self.info_bar.addWidget(self.progress_ring)
self.info_bar.closeButton.clicked.connect(self.stop_download) # 关闭即中断下载
self.info_bar.show()
# 启动下载线程
self.download_thread = DownloadThread( self.download_thread = DownloadThread(
files, self.SERVER_URL, self.SECRET_KEY, self.LOCAL_LIB_DIR files, self.SERVER_URL, self.SECRET_KEY, self.LOCAL_LIB_DIR
) )
self.download_thread.progressChanged.connect(self.progress_bar.setValue) self.download_thread.progressChanged.connect(self.progress_ring.setValue)
self.download_thread.finished.connect(self.on_download_finished) self.download_thread.finished.connect(self.on_download_finished)
self.download_thread.finished.connect(self.download_thread.deleteLater) self.download_thread.finished.connect(self.download_thread.deleteLater)
self.download_thread.start() self.download_thread.start()
def stop_download(self):
if hasattr(self, "download_thread") and self.download_thread.isRunning():
self.download_thread.terminate()
self.download_thread.wait()
self.info_bar.close()
InfoBar.warning(
title="下载已中断",
content="已手动中断下载任务。",
parent=self,
position=InfoBarPosition.TOP,
duration=2000
)
def on_download_finished(self, success, fail): def on_download_finished(self, success, fail):
self.progress_dialog.close() self.info_bar.close()
msg = f"成功下载: {len(success)} 个文件\n失败: {len(fail)} 个文件" msg = f"成功下载:{len(success)} 个文件,失败:{len(fail)} 个文件"
dialog = Dialog(
title="下载结果", # 创建“打开文件夹”按钮
content=msg,
parent=self
)
open_btn = PushButton("打开文件夹") open_btn = PushButton("打开文件夹")
def open_folder(): def open_folder():
folder = os.path.abspath(self.LOCAL_LIB_DIR) folder = os.path.abspath(self.LOCAL_LIB_DIR)
@ -196,10 +180,18 @@ class PartLibraryInterface(QWidget):
subprocess.call(["explorer", folder]) subprocess.call(["explorer", folder])
else: else:
subprocess.call(["xdg-open", folder]) subprocess.call(["xdg-open", folder])
dialog.close()
# 展示成功消息条,自动消失
self.result_bar = InfoBar.success(
title="下载完成",
content=msg,
parent=self,
position=InfoBarPosition.TOP,
duration=4000 # 4秒后自动消失
)
self.result_bar.addWidget(open_btn)
open_btn.clicked.connect(open_folder) open_btn.clicked.connect(open_folder)
dialog.textLayout.addWidget(open_btn) self.result_bar.show()
dialog.exec()
def open_local_lib(self): def open_local_lib(self):
folder = os.path.abspath(self.LOCAL_LIB_DIR) folder = os.path.abspath(self.LOCAL_LIB_DIR)

View File

@ -2,19 +2,13 @@ import requests
from packaging.version import parse as vparse from packaging.version import parse as vparse
def check_update(local_version, repo="goldenfishs/MRobot"): def check_update(local_version, repo="goldenfishs/MRobot"):
"""
检查 GitHub 上是否有新版本
:param local_version: 当前版本号字符串 "1.0.2"
:param repo: 仓库名格式 "用户名/仓库名"
:return: 最新版本号字符串如果有新版本否则 None
"""
url = f"https://api.github.com/repos/{repo}/releases/latest" url = f"https://api.github.com/repos/{repo}/releases/latest"
try: resp = requests.get(url, timeout=5)
resp = requests.get(url, timeout=5) if resp.status_code == 200:
if resp.status_code == 200: latest = resp.json()["tag_name"].lstrip("v")
latest = resp.json()["tag_name"].lstrip("v") if vparse(latest) > vparse(local_version):
if vparse(latest) > vparse(local_version): return latest
return latest else:
except Exception as e: return None
print(f"检查更新失败: {e}") else:
return None raise RuntimeError("GitHub API 请求失败")

View File

@ -0,0 +1,45 @@
from PyQt5.QtCore import QThread, pyqtSignal
import requests
import shutil
import os
from urllib.parse import quote
class DownloadThread(QThread):
progressChanged = pyqtSignal(int)
finished = pyqtSignal(list, list) # success, fail
def __init__(self, files, server_url, secret_key, local_dir, parent=None):
super().__init__(parent)
self.files = files
self.server_url = server_url
self.secret_key = secret_key
self.local_dir = local_dir
def run(self):
success, fail = [], []
total = len(self.files)
max_retry = 3
for idx, rel_path in enumerate(self.files):
retry = 0
while retry < max_retry:
try:
rel_path_unix = rel_path.replace("\\", "/")
encoded_path = quote(rel_path_unix)
url = f"{self.server_url}/download/{encoded_path}"
params = {"key": self.secret_key}
resp = requests.get(url, params=params, stream=True, timeout=10)
if resp.status_code == 200:
local_path = os.path.join(self.local_dir, rel_path)
os.makedirs(os.path.dirname(local_path), exist_ok=True)
with open(local_path, "wb") as f:
shutil.copyfileobj(resp.raw, f)
success.append(rel_path)
break
else:
retry += 1
except Exception:
retry += 1
else:
fail.append(rel_path)
self.progressChanged.emit(int((idx + 1) / total * 100))
self.finished.emit(success, fail)

114
app/tools/task_config.py Normal file
View File

@ -0,0 +1,114 @@
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout
from qfluentwidgets import TitleLabel, BodyLabel, TableWidget, PushButton, SubtitleLabel, SpinBox, InfoBar, InfoBarPosition, LineEdit, CheckBox
from PyQt5.QtCore import Qt
import yaml
import os
class TaskConfigDialog(QDialog):
def __init__(self, parent=None, config_path=None):
super().__init__(parent)
self.setWindowTitle("任务配置")
self.resize(900, 480)
layout = QVBoxLayout(self)
layout.setContentsMargins(32, 32, 32, 32)
layout.setSpacing(18)
layout.addWidget(TitleLabel("FreeRTOS 任务配置"))
layout.addWidget(BodyLabel("请添加并配置您的任务参数,支持频率控制与描述。"))
self.table = TableWidget(self)
self.table.setColumnCount(6)
self.table.setHorizontalHeaderLabels(["任务名称", "运行频率", "初始化延迟", "堆栈大小", "任务描述", "频率控制"])
self.table.horizontalHeader().setSectionResizeMode(0, self.table.horizontalHeader().Stretch)
self.table.horizontalHeader().setSectionResizeMode(1, self.table.horizontalHeader().ResizeToContents)
self.table.horizontalHeader().setSectionResizeMode(2, self.table.horizontalHeader().ResizeToContents)
self.table.horizontalHeader().setSectionResizeMode(3, self.table.horizontalHeader().ResizeToContents)
self.table.horizontalHeader().setSectionResizeMode(4, self.table.horizontalHeader().Stretch)
self.table.horizontalHeader().setSectionResizeMode(5, self.table.horizontalHeader().ResizeToContents)
self.table.setMinimumHeight(260)
layout.addWidget(self.table)
btn_layout = QHBoxLayout()
self.add_btn = PushButton("添加任务")
self.del_btn = PushButton("删除选中")
self.ok_btn = PushButton("生成")
self.cancel_btn = PushButton("取消")
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.del_btn)
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
layout.addLayout(btn_layout)
self.add_btn.clicked.connect(self.add_row)
self.del_btn.clicked.connect(self.del_row)
self.ok_btn.clicked.connect(self.accept)
self.cancel_btn.clicked.connect(self.reject)
# 自动读取配置文件
if config_path and os.path.exists(config_path):
try:
with open(config_path, "r", encoding="utf-8") as f:
tasks = yaml.safe_load(f)
if tasks:
for t in tasks:
self.add_row(t)
except Exception:
pass
def add_row(self, task=None):
row = self.table.rowCount()
self.table.insertRow(row)
name = LineEdit(task.get("name", f"Task{row+1}") if task else f"Task{row+1}")
freq = SpinBox()
freq.setRange(1, 10000)
freq.setValue(task.get("frequency", 500) if task else 500)
delay = SpinBox()
delay.setRange(0, 10000)
delay.setValue(task.get("delay", 0) if task else 0)
stack = SpinBox()
stack.setRange(128, 8192)
stack.setSingleStep(128)
stack.setValue(task.get("stack", 256) if task else 256)
desc = LineEdit(task.get("description", "") if task else "请填写任务描述")
freq_ctrl = CheckBox("启用")
freq_ctrl.setChecked(task.get("freq_control", True) if task else True)
self.table.setCellWidget(row, 0, name)
self.table.setCellWidget(row, 1, freq)
self.table.setCellWidget(row, 2, delay)
self.table.setCellWidget(row, 3, stack)
self.table.setCellWidget(row, 4, desc)
self.table.setCellWidget(row, 5, freq_ctrl)
def del_row(self):
selected = self.table.selectedItems()
if selected:
rows = set(item.row() for item in selected)
for row in sorted(rows, reverse=True):
self.table.removeRow(row)
def get_tasks(self):
tasks = []
for row in range(self.table.rowCount()):
name = self.table.cellWidget(row, 0).text().strip()
freq = self.table.cellWidget(row, 1).value()
delay = self.table.cellWidget(row, 2).value()
stack = self.table.cellWidget(row, 3).value()
desc = self.table.cellWidget(row, 4).text().strip()
freq_ctrl = self.table.cellWidget(row, 5).isChecked()
# 校验 stack 必须为 128*2^n
if stack < 128 or (stack & (stack - 1)) != 0 or stack % 128 != 0:
raise ValueError(f"{row+1}行任务“{name}”的堆栈大小必须为128、256、512、1024等128*2^n")
task = {
"name": name,
"function": f"Task_{name}",
"delay": delay,
"stack": stack,
"description": desc,
"freq_control": freq_ctrl
}
if freq_ctrl:
task["frequency"] = freq
tasks.append(task)
return tasks

BIN
mech_lib/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.