乐于分享
好东西不私藏

ChemDraw JavaScript插件开发

ChemDraw JavaScript插件开发

ChemDraw JavaScript插件开发

activeDocument对象有以下属性:addCDXBase64EncodedaddCDXBase64EncodedIgnoringSettingsaddCDXMLaddCDXMLIgnoringSettingsaddInChIaddMoladdMolV2000addMolV3000addRXNV2000addRXNV3000addSMILEScleargetCDXBase64EncodedgetCDXMLgetInChIgetInChIKeygetMolV2000getMolV3000getPNGBase64EncodedgetRXNV2000getRXNV3000getSMILESselection,共计23个属性值。ChemDrawAPI共有7个属性:activeDocumentcopyToClipboardhandlersopenURLInDefaultBrowserregisterURLTriggeredCallbackversionwindow,ChemDrawAPI属性方法可分为以下几类:

1. 全局入口与环境(ChemDrawAPI 对象)

成员
类型
说明
activeDocument
属性
当前打开的文档对象(下一步操作的核心)
window
属性
插件容器窗口对象(控制窗口行为)
version
属性
当前 JavaScript API 的版本号
openURLInDefaultBrowser(url)
方法
在系统默认浏览器打开网页
copyToClipboard(text)
方法
复制文本到剪贴板
registerURLTriggeredCallback(fn)
方法
注册外部 URL 调用的回调函数(高级)

ChemDrawAPI对象属性中最重要的是activeDocument属性,有关于文档以及对象的操作全部在此属性中

2. 文档内容读写(Document 类)

这是最核心的一类,负责在 ChemDraw 文档中导入和导出各种化学数据格式,所有方法都挂在 ChemDrawAPI.activeDocument 下。

  • 获取文档的选择对象selection 属性
  • 通用格式导入/导出
操作
导入 (add)
导出 (get)
CDXML
addCDXML(text) getCDXML()
CDX (Base64)
addCDXBase64Encoded(text) getCDXBase64Encoded()
SMILES
addSMILES(text) getSMILES()
InChI
addInChI(text) getInChI()
InChIKey
—(不可逆,InChIKey格式特点,无法逆向)
getInChIKey()
Mol V2000
addMolV2000(text) getMolV2000()
Mol V3000
addMolV3000(text) getMolV3000()
自动识别 Mol
addMol(text)
—(需要指定Mol版本方法,如v2000或v3000)
RXN V2000
addRXNV2000(text) getRXNV2000()
RXN V3000
addRXNV3000(text) getRXNV3000()
PNG 图片
—(ChemDraw不支持图片)
getPNGBase64Encoded([options])

3. 窗口管理(Window 类)

控制插件容器界面的外观和行为,所有方法通过 ChemDrawAPI.window 调用。

方法
说明
setDefaultSize(width, height)
设置窗口默认尺寸
resizeTo(width, height)
直接调整窗口大小(已弃用)
close()
关闭插件容器窗口
onClose(callback)
窗口即将关闭时执行的回调

4. 选择区操作(Selection 类)

用于读取文档中用户框选的内容,也能监听选择变化。 对象来源:ChemDrawAPI.activeDocument.selection

成员
类型
说明
isEmpty()
方法
判断是否未选中任何对象
containsPartialStructure()
方法
判断是否只选中了某个结构的一部分
getCDXML()
方法
导出选中部分为 CDXML
getSVG([options])
方法
导出选中部分为 SVG 图像
getInChIKey()
方法
导出选中结构的 InChIKey
getSMILES()
方法
导出选中结构的 SMILES
onChange(callback)
方法
当选区改变时触发回调

5. 图像导出选项(ImageOption 结构)

用于 getPNGBase64Encoded() 和 getSVG() 的传参,不是类,而是一个 JSON 配置对象

属性
类型
默认值
说明
transparent
boolean
true
背景是否透明
scalePercent
integer
100
缩放百分比(必须 > 0)
borderSizeInPixels
integer
0
图片四周边框像素数(必须 ≥ 0)

单个去讲每个方法和属性的应用太过繁琐,直接上实例学习

实例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({transparentfalse,scalePercent100,borderSizeInPixels0            });// 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; padding20px; }#output { white-space: pre-wrap; font-family: monospace; margin-top20px; }button { padding10px20pxfont-size16pxcursor: 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;padding15px;background-color: WhiteSmoke;        }input[type="text"] {padding8px;width200px;margin-right5px;border1px solid #ccc;border-radius4px;        }button {padding8px15px;background-color#007bff;color: white;border: none;border-radius4px;cursor: pointer;        }button:hover {background-color#0056b3;        }#output {margin-top15px;padding10px;background-color#fff;border1px solid #ddd;border-radius4px;white-space: pre-wrap;font-family: monospace;font-size0.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({ transparenttruescalePercent100borderSizeInPixels0 });            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(00, w, h);                ctx.drawImage(img, 00, 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; padding15pxbackground: WhiteSmoke; }button { padding8px20pxfont-size14px; }#output { margin-top10pxwhite-space: pre-wrap; font-family: monospace; background#fffborder1px solid #cccpadding8px; }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>