feat(biochem): Фаза 7 — импорт SMILES + экспорт PNG/JSON
BIO.parseSmiles — парсер учебного подмножества SMILES (органические атомы верхнего регистра, связи -=#, ветви (), замыкание циклов цифрами/%nn, неявные H по валентности, 2D-укладка BFS). BIO.toJSON/download. biochem.html: поле ввода SMILES + кнопка Импорт (Enter), кнопки экспорта PNG (текущий холст 2D/3D) и JSON. Проверено: CCO→C2H6O, CC(=O)O→C2H4O2, C1=CC=CC=C1→C6H6 (Кекуле), ClC(Cl)(Cl)Cl→CCl4, OCC(O)CO→C3H8O3 (глицерин); мусор отсекается. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -435,6 +435,18 @@
|
||||
<button class="bp-btn bp-btn-secondary" onclick="loadFromLibrary()">
|
||||
Загрузить из библиотеки
|
||||
</button>
|
||||
<!-- SMILES import -->
|
||||
<div style="display:flex;gap:6px;margin-top:8px">
|
||||
<input type="text" id="smiles-in" placeholder="SMILES, напр. CCO" spellcheck="false"
|
||||
onkeydown="if(event.key==='Enter')importSmiles()"
|
||||
style="flex:1;min-width:0;padding:7px 10px;border-radius:8px;background:rgba(255,255,255,.06);border:1.5px solid rgba(255,255,255,.12);color:#ddd;font:600 .78rem monospace">
|
||||
<button class="bp-btn bp-btn-secondary" style="width:auto;margin:0;padding:0 14px" onclick="importSmiles()">Импорт</button>
|
||||
</div>
|
||||
<!-- Export -->
|
||||
<div style="display:flex;gap:6px;margin-top:6px">
|
||||
<button class="bp-btn bp-btn-secondary" style="margin:0;flex:1" onclick="exportPNG()">PNG</button>
|
||||
<button class="bp-btn bp-btn-secondary" style="margin:0;flex:1" onclick="exportJSON()">JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-section" id="bp-active-challenge" style="display:none">
|
||||
<div class="bp-label" id="bp-chal-type-label">Текущее задание</div>
|
||||
@@ -1260,6 +1272,42 @@ async function saveCurrentMolecule() {
|
||||
} catch(e) { LS.toast('Ошибка: '+e.message, 'error'); }
|
||||
}
|
||||
|
||||
// ── SMILES import ──
|
||||
function importSmiles() {
|
||||
const inp = document.getElementById('smiles-in');
|
||||
const smi = (inp.value || '').trim();
|
||||
if (!smi) return;
|
||||
const parsed = BIO.parseSmiles(smi);
|
||||
if (!parsed || !parsed.atoms.length) {
|
||||
LS.toast('Не удалось разобрать SMILES (поддержан верхний регистр: CCO, C1=CC=CC=C1)', 'error');
|
||||
return;
|
||||
}
|
||||
pushHistory();
|
||||
// переносим в редактор (bonds в формате {from,to,order})
|
||||
const idMap = {};
|
||||
atoms = parsed.atoms.map(a => { const nid = nextId++; idMap[a.id] = nid; return { id: nid, s: a.s, x: a.x, y: a.y }; });
|
||||
bonds = parsed.bonds.map(b => ({ id: nextId++, from: idMap[b.f], to: idMap[b.t], order: b.o }));
|
||||
inp.value = '';
|
||||
if (_is3D) _build3D();
|
||||
centerView(); updateInfo();
|
||||
LS.toast(`Импортировано: ${BIO.hillFormula(atoms)}`, 'success');
|
||||
}
|
||||
|
||||
// ── Export ──
|
||||
function exportPNG() {
|
||||
if (!atoms.length) { LS.toast('Пустой холст', 'info'); return; }
|
||||
const a = document.createElement('a');
|
||||
a.href = canvas.toDataURL('image/png');
|
||||
a.download = (hillFormula() || 'molecule') + (_is3D ? '-3d' : '') + '.png';
|
||||
a.click();
|
||||
}
|
||||
function exportJSON() {
|
||||
if (!atoms.length) { LS.toast('Пустой холст', 'info'); return; }
|
||||
BIO.download((hillFormula() || 'molecule') + '.json',
|
||||
BIO.toJSON(atoms, bonds.map(b => ({ f: b.from, t: b.to, o: b.order })), hillFormula()),
|
||||
'application/json');
|
||||
}
|
||||
|
||||
// ── Library ──
|
||||
async function loadFromLibrary() {
|
||||
if (!_libAll.length) _libAll = await LS.biochemGetMolecules();
|
||||
|
||||
Reference in New Issue
Block a user