ChemDraw JavaScript插件开发
ChemDraw JavaScript插件开发
activeDocument对象有以下属性:addCDXBase64Encoded、addCDXBase64EncodedIgnoringSettings、addCDXML、addCDXMLIgnoringSettings、addInChI、addMol、addMolV2000、addMolV3000、addRXNV2000、addRXNV3000、addSMILES、clear、getCDXBase64Encoded、getCDXML、getInChI、getInChIKey、getMolV2000、getMolV3000、getPNGBase64Encoded、getRXNV2000、getRXNV3000、getSMILES、selection,共计23个属性值。ChemDrawAPI共有7个属性:activeDocument、copyToClipboard、handlers、openURLInDefaultBrowser、registerURLTriggeredCallback、version、window,ChemDrawAPI属性方法可分为以下几类:
1. 全局入口与环境(ChemDrawAPI 对象)
|
|
|
|
|---|---|---|
activeDocument |
|
|
window |
|
|
version |
|
|
openURLInDefaultBrowser(url) |
|
|
copyToClipboard(text) |
|
|
registerURLTriggeredCallback(fn) |
|
|
ChemDrawAPI对象属性中最重要的是activeDocument属性,有关于文档以及对象的操作全部在此属性中
2. 文档内容读写(Document 类)
这是最核心的一类,负责在 ChemDraw 文档中导入和导出各种化学数据格式,所有方法都挂在 ChemDrawAPI.activeDocument 下。
-
获取文档的选择对象 selection属性 -
通用格式导入/导出
|
|
|
|
|---|---|---|
|
|
addCDXML(text) |
getCDXML() |
|
|
addCDXBase64Encoded(text) |
getCDXBase64Encoded() |
|
|
addSMILES(text) |
getSMILES() |
|
|
addInChI(text) |
getInChI() |
|
|
|
getInChIKey() |
|
|
addMolV2000(text) |
getMolV2000() |
|
|
addMolV3000(text) |
getMolV3000() |
|
|
addMol(text) |
|
|
|
addRXNV2000(text) |
getRXNV2000() |
|
|
addRXNV3000(text) |
getRXNV3000() |
|
|
|
getPNGBase64Encoded([options]) |
3. 窗口管理(Window 类)
控制插件容器界面的外观和行为,所有方法通过 ChemDrawAPI.window 调用。
|
|
|
|---|---|
setDefaultSize(width, height) |
|
resizeTo(width, height) |
|
close() |
|
onClose(callback) |
|
4. 选择区操作(Selection 类)
用于读取文档中用户框选的内容,也能监听选择变化。 对象来源:ChemDrawAPI.activeDocument.selection。
|
|
|
|
|---|---|---|
isEmpty() |
|
|
containsPartialStructure() |
|
|
getCDXML() |
|
|
getSVG([options]) |
|
|
getInChIKey() |
|
|
getSMILES() |
|
|
onChange(callback) |
|
|
5. 图像导出选项(ImageOption 结构)
用于 getPNGBase64Encoded() 和 getSVG() 的传参,不是类,而是一个 JSON 配置对象。
|
|
|
|
|
|---|---|---|---|
transparent |
|
|
|
scalePercent |
|
|
|
borderSizeInPixels |
|
|
|
单个去讲每个方法和属性的应用太过繁琐,直接上实例学习
实例1:获取当前文档结构式图片到剪切板
ChemDraw JS API仅支持向剪切板写入文本数据,不支持写入图片数据,所以要进行迂回策略。首先获取当前文档对象,借助activeDocument属性的getPNGBase64Encoded()方法获取图片的base64编码,在插件容器中创建一个Blod对象,通过Blod对象将图片写入到剪切板。
Tips:这个操作在chemdraw应用中是很方便的

//// main.js// 复制当前文档结构图为 PNG 到剪贴板///* global ChemDrawAPI */functiononLoadBody() {var output = document.getElementById("output");functionlog(msg) { output.innerText += msg + "\n"; }// 触发复制的函数,通过按钮调用window.copyImage = asyncfunction () {try {// 1. 获取 PNG Base64(可加选项:透明背景等)var base64 = ChemDrawAPI.activeDocument.getPNGBase64Encoded({transparent: false,scalePercent: 100,borderSizeInPixels: 0 });// 2. 将 Base64 转为 Blobvar byteString = atob(base64);var mimeString = "image/png";var ab = newArrayBuffer(byteString.length);var ia = newUint8Array(ab);for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); }var blob = new Blob([ab], { type: mimeString });// 3. 写入剪贴板await navigator.clipboard.write([new ClipboardItem({ "image/png": blob }) ]); log("✅ 图片已复制到剪贴板!"); } catch (e) { log("❌ 复制失败: " + e.message); } }; log("点击按钮复制文档结构图到剪贴板");}
前端页面
<!DOCTYPE html><html><metacharset="UTF-8"><scriptsrc="main.js"></script><head><style>body { background-color: WhiteSmoke; font-family: sans-serif; padding: 20px; }#output { white-space: pre-wrap; font-family: monospace; margin-top: 20px; }button { padding: 10px20px; font-size: 16px; cursor: pointer; }</style><title>Copy Structure Image</title></head><bodyonload="onLoadBody()"><h2>复制结构式图片</h2><hr/><buttononclick="copyImage()">📋 复制到剪贴板</button><divid="output"></div></body></html>
实例2:CAS号转结构式
默认使用乐研网页api进行,备用pubchem
/* global ChemDrawAPI */functiononLoadBody() {var casInput = document.getElementById('casInput');var drawButton = document.getElementById('drawButton');var outputDiv = document.getElementById('output');functionlog(msg) { outputDiv.innerText += msg + '\n'; }// ---------- 核心:通过乐妍 API 获取 SMILES ----------functionfetchSmilesFromLeyan(cas) {var url = 'https://structural.leyan.com/ly/findSmiles';var requestBody = JSON.stringify({ cas: cas });// 仅保留必要的头部,cookie 无法在客户端硬塞return fetch(url, {method: 'POST',headers: {'Content-Type': 'application/json;charset=UTF-8','Accept': 'application/json, text/javascript, */*','X-Requested-With': 'XMLHttpRequest'// 有的后端会用这个判断// 注意:不携带 cookie,因为插件环境没有 },mode: 'cors', // 浏览器跨域请求body: requestBody }) .then(function(response) {if (!response.ok) thrownewError('乐妍 API 返回状态 ' + response.status);return response.json(); }) .then(function(data) {// 尝试从常见的 JSON 结构中提取 SMILES,这里需要你根据实际返回调整var smiles = data.smiles || (data.data && data.data.smiles);if (!smiles) {// 如果结构未知,打印整个 data 方便调试 log('⚠️ 乐妍返回数据格式未识别: ' + JSON.stringify(data));thrownewError('无法从乐妍返回值中解析出 SMILES'); }return smiles; }); }// ---------- 备用:通过 PubChem 获取 SMILES ----------functionfetchSmilesFromPubchem(cas) {var url = 'https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/' + cas + '/property/IsomericSMILES/JSON';return fetch(url, { mode: 'cors' }) .then(function(response) {if (!response.ok) thrownewError('PubChem API 返回状态 ' + response.status);return response.json(); }) .then(function(data) {var smiles = data.PropertyTable.Properties[0].SMILES;if (!smiles) thrownewError('PubChem 返回值中无 SMILES');return smiles; }); }// ---------- 串联流程 ----------functionqueryAndDraw() {var cas = casInput.value.trim();if (!cas) { log('❌ 请输入 CAS 号');return; } outputDiv.innerText = ''; // 清屏 log('🔍 正在查询 CAS: ' + cas);// 先尝试乐妍 fetchSmilesFromLeyan(cas) .then(function(smiles) { log('✅ 已通过乐妍获取到 SMILES: ' + smiles);return smiles; }) .catch(function(leyanError) { log('⚡ 乐妍查询失败(' + leyanError.message + '),自动切换为 PubChem...');return fetchSmilesFromPubchem(cas); }) .then(function(smiles) { log('📝 最终使用的 SMILES: ' + smiles);// 检查 ChemDraw 环境if (typeof ChemDrawAPI === 'undefined' || !ChemDrawAPI.activeDocument) {thrownewError('未检测到 ChemDraw 活动文档,请先打开一个文档'); } ChemDrawAPI.activeDocument.addSMILES(smiles); log('🎉 结构已成功添加到文档中!'); }) .catch(function(finalError) { log('❌ 操作失败: ' + finalError.message); }); }// 绑定按钮 drawButton.addEventListener('click', queryAndDraw); log('准备就绪,输入 CAS 号并点击按钮。');}
对应前端
<!DOCTYPE html><html><head><metacharset="UTF-8"><title>CAS to Smiles</title><style>body {font-family: sans-serif;padding: 15px;background-color: WhiteSmoke; }input[type="text"] {padding: 8px;width: 200px;margin-right: 5px;border: 1px solid #ccc;border-radius: 4px; }button {padding: 8px15px;background-color: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer; }button:hover {background-color: #0056b3; }#output {margin-top: 15px;padding: 10px;background-color: #fff;border: 1px solid #ddd;border-radius: 4px;white-space: pre-wrap;font-family: monospace;font-size: 0.9em; }</style></head><bodyonload="onLoadBody()"><h2>CAS号 → 化学结构式</h2><hr><div><labelfor="casInput">输入CAS号:</label><inputtype="text"id="casInput"placeholder="例如:50-78-2"><buttonid="drawButton">查询并绘制</button></div><divid="output"></div><scriptsrc="main.js"></script></body></html>
实例3、识别图片中的结构式
借用chemicalbook的网页识别接口,需要注意的chemdraw插件不允许跨域,需要借助后端实现
后端
from flask import Flask, jsonify, request, Responseimport requestsimport base64from flask_cors import CORSimport urllib3import timeurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)ocr_app = Flask(__name__)CORS(ocr_app)@ocr_app.post("/api/img2structure/chemicalbook")defimg2StructureByChemicalBook():""" Request: img_base64_string: 图片的base64编码字符串,需包含data:image/png;base64, Response: structureMolString: 识别出的结构式mol v2000字符串 """# 1. 获取并验证输入数据 flask_request_data = request.get_json()ifnot flask_request_data:return Response("Missing JSON body", status=400, mimetype="text/plain") img_base64_string = flask_request_data.get("img_data")ifnot img_base64_string:return Response("Missing 'img_data' field", status=400, mimetype="text/plain")# 2. 准备请求参数 request_url = "https://www.chemicalbook.com/Handler/GetStructure.ashx" payload = {"data": img_base64_string, "methodName": "GetStructureOfImg"} headers = {"X-Requested-With": "XMLHttpRequest","Content-Type": "application/x-www-form-urlencoded; charset=UTF-8","User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", }# 3. 发送请求并处理异常try: result = requests.post( url=request_url, headers=headers, data=payload, timeout=30, # 设置超时,避免长时间等待 )except requests.exceptions.Timeout:return Response("Request timeout", status=504, mimetype="text/plain")except requests.exceptions.ConnectionError:return Response("Connection error", status=502, mimetype="text/plain")except requests.exceptions.RequestException as e:return Response(f"Request failed: {str(e)}", status=500, mimetype="text/plain")# 4. 检查HTTP状态码if result.status_code != 200:return Response(f"Upstream error: HTTP {result.status_code}", status=500, mimetype="text/plain", )# 5. 解析响应内容 mol_text = result.text# 6. 验证响应有效性(检查是否包含有效的V2000格式)# V2000格式的第一行末尾通常包含"V2000",且整个文本应包含必要的结构信息if"V2000"notin mol_text or len(mol_text.strip()) < 100: # 简单有效性检查return Response("Invalid structure data", status=500, mimetype="text/plain")# 7. 返回成功结果return Response(mol_text, mimetype="text/plain")if __name__ == "__main__": ocr_app.run(host="127.0.0.1", port=18888, debug=True)
前端js
/* global ChemDrawAPI */functiononLoadBody() {var output = document.getElementById('output');var btn = document.getElementById('convertBtn');var canvas = document.getElementById('hiddenCanvas');functionlog(msg) { output.innerText += msg + '\n'; } btn.onclick = function() { output.innerText = ''; log('===== 开始识别 =====');// 1. 获取选区 SVGtry {var sel = ChemDrawAPI.activeDocument.selection;if (sel.isEmpty()) { log('❌ 没有选中任何内容'); return; }var svg = sel.getSVG({ transparent: true, scalePercent: 100, borderSizeInPixels: 0 }); log('✅ 已获取选区 SVG'); } catch (e) { log('❌ 获取 SVG 失败: ' + e.message);return; }// 2. 渲染为 PNG 并提取 Base64try {var img = new Image();var ctx = canvas.getContext('2d'); img.onload = function() {var w = img.width, h = img.height;if (w > 800 || h > 600) {var ratio = Math.min(800 / w, 600 / h); w *= ratio; h *= ratio; } canvas.width = w; canvas.height = h; ctx.clearRect(0, 0, w, h); ctx.drawImage(img, 0, 0, w, h);var dataUrl = canvas.toDataURL('image/png'); // data:image/png;base64,... log('✅ PNG 转换成功');// 3. 发送请求var url = 'http://127.0.0.1:18888/api/img2structure/chemicalbook';var postData = JSON.stringify({ img_data: dataUrl }); // 直接传完整 Data URL fetch(url, {method: 'POST',headers: { 'Content-Type': 'application/json; charset=UTF-8' },body: postData }) .then(function(resp) {if (!resp.ok) thrownewError('HTTP ' + resp.status);return resp.text(); }) .then(function(molfile) { log('✅ 识别成功,Molfile 长度: ' + molfile.length);// 4. 绘制到文档try { ChemDrawAPI.activeDocument.addMol(molfile); log('🎉 结构已绘制!'); } catch (e) { log('❌ 绘制失败: ' + e.message); } }) .catch(function(err) { log('❌ 请求错误: ' + err.message); }); }; img.onerror = function() { log('❌ SVG 加载失败(可能选区内容无法渲染)'); };var blob = new Blob([svg], { type: 'image/svg+xml' }); img.src = URL.createObjectURL(blob); } catch (e) { log('❌ 转换 PNG 失败: ' + e.message); } }; log('插件就绪。确认后端已启动,打开文档,选中一张图片后点击按钮。');}
前端页面
<!DOCTYPE html><html><metacharset="UTF-8"><scriptsrc="main.js"></script><head><style>body { font-family: sans-serif; padding: 15px; background: WhiteSmoke; }button { padding: 8px20px; font-size: 14px; }#output { margin-top: 10px; white-space: pre-wrap; font-family: monospace; background: #fff; border: 1px solid #ccc; padding: 8px; }canvas { display: none; }</style><title>识图演示</title></head><bodyonload="onLoadBody()"><h3>图片识别演示</h3><buttonid="convertBtn">识别选中图片</button><canvasid="hiddenCanvas"width="800"height="600"></canvas><divid="output"></div></body></html>
夜雨聆风