moleditpy 1.2.1__tar.gz → 1.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moleditpy
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
5
5
  Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/HiroYokoyama/python_molecular_editor
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "moleditpy"
7
7
 
8
- version = "1.2.1"
8
+ version = "1.2.2"
9
9
 
10
10
  authors = [
11
11
  { name="HiroYokoyama", email="titech.yoko.hiro@gmail.com" },
@@ -11,7 +11,7 @@ DOI 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
13
  #Version
14
- VERSION = '1.2.1'
14
+ VERSION = '1.2.2'
15
15
 
16
16
  print("-----------------------------------------------------")
17
17
  print("MoleditPy — A Python-based molecular editing software")
@@ -39,7 +39,7 @@ from PyQt6.QtWidgets import (
39
39
  )
40
40
  from PyQt6.QtGui import (
41
41
  QPen, QBrush, QColor, QPainter, QAction, QActionGroup, QFont, QPolygonF,
42
- QPainterPath, QPainterPathStroker, QFontMetrics, QKeySequence, QTransform, QCursor, QPixmap, QIcon, QShortcut, QDesktopServices
42
+ QPainterPath, QPainterPathStroker, QFontMetrics, QFontMetricsF, QKeySequence, QTransform, QCursor, QPixmap, QIcon, QShortcut, QDesktopServices
43
43
  )
44
44
  from PyQt6.QtCore import Qt, QPointF, QRectF, QLineF, QObject, QThread, pyqtSignal, QEvent, QMimeData, QByteArray, QUrl
45
45
 
@@ -257,16 +257,81 @@ class AtomItem(QGraphicsItem):
257
257
  super().__init__()
258
258
  self.atom_id, self.symbol, self.charge, self.radical, self.bonds, self.chiral_label = atom_id, symbol, charge, radical, [], None
259
259
  self.setPos(pos)
260
+ self.implicit_h_count = 0
260
261
  self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
261
262
  self.setZValue(1); self.font = QFont("Arial", 20, QFont.Weight.Bold); self.update_style()
262
263
  self.setAcceptHoverEvents(True)
263
264
  self.hovered = False
264
265
  self.has_problem = False
265
-
266
+
266
267
  def boundingRect(self):
267
- r = globals().get('ATOM_RADIUS', 18)
268
- extra = 15.0
269
- return QRectF(-r - extra, -r - extra, (r + extra) * 2.0, (r + extra) * 2.0)
268
+ # --- paint()メソッドと完全に同じロジックでテキストの位置とサイズを計算 ---
269
+ font = QFont("Arial", 20, QFont.Weight.Bold)
270
+ fm = QFontMetricsF(font)
271
+
272
+ hydrogen_part = ""
273
+ if self.implicit_h_count > 0:
274
+ is_skeletal_carbon = (self.symbol == 'C' and len(self.bonds) > 0)
275
+ if not is_skeletal_carbon:
276
+ hydrogen_part = "H"
277
+ if self.implicit_h_count > 1:
278
+ subscript_map = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
279
+ hydrogen_part += str(self.implicit_h_count).translate(subscript_map)
280
+
281
+ flip_text = False
282
+ if hydrogen_part and self.bonds:
283
+ my_pos_x = self.pos().x()
284
+ total_dx = sum((b.atom2.pos().x() if b.atom1 is self else b.atom1.pos().x()) - my_pos_x for b in self.bonds)
285
+ if total_dx > 0:
286
+ flip_text = True
287
+
288
+ if flip_text:
289
+ display_text = hydrogen_part + self.symbol
290
+ else:
291
+ display_text = self.symbol + hydrogen_part
292
+
293
+ text_rect = fm.boundingRect(display_text)
294
+ text_rect.adjust(-2, -2, 2, 2)
295
+ if hydrogen_part:
296
+ symbol_rect = fm.boundingRect(self.symbol)
297
+ if flip_text:
298
+ offset_x = symbol_rect.width() // 2
299
+ text_rect.moveTo(offset_x - text_rect.width(), -text_rect.height() / 2)
300
+ else:
301
+ offset_x = -symbol_rect.width() // 2
302
+ text_rect.moveTo(offset_x, -text_rect.height() / 2)
303
+ else:
304
+ text_rect.moveCenter(QPointF(0, 0))
305
+
306
+ # 1. paint()で描画される背景の矩形(bg_rect)を計算する
307
+ bg_rect = text_rect.adjusted(-5, -8, 5, 8)
308
+
309
+ # 2. このbg_rectを基準として全体の描画領域を構築する
310
+ full_visual_rect = QRectF(bg_rect)
311
+
312
+ # 電荷記号の領域を計算に含める
313
+ if self.charge != 0:
314
+ if self.charge == 1: charge_str = "+"
315
+ elif self.charge == -1: charge_str = "-"
316
+ else: charge_str = f"{self.charge:+}"
317
+ charge_font = QFont("Arial", 12, QFont.Weight.Bold)
318
+ charge_fm = QFontMetricsF(charge_font)
319
+ charge_rect = charge_fm.boundingRect(charge_str)
320
+
321
+ if flip_text:
322
+ charge_pos = QPointF(text_rect.left() - charge_rect.width() - 2, text_rect.top())
323
+ else:
324
+ charge_pos = QPointF(text_rect.right() + 2, text_rect.top())
325
+ charge_rect.moveTopLeft(charge_pos)
326
+ full_visual_rect = full_visual_rect.united(charge_rect)
327
+
328
+ # ラジカル記号の領域を計算に含める
329
+ if self.radical > 0:
330
+ radical_area = QRectF(text_rect.center().x() - 8, text_rect.top() - 8, 16, 8)
331
+ full_visual_rect = full_visual_rect.united(radical_area)
332
+
333
+ # 3. 選択ハイライト等のための最終的なマージンを追加する
334
+ return full_visual_rect.adjusted(-3, -3, 3, 3)
270
335
 
271
336
  def shape(self):
272
337
  scene = self.scene()
@@ -287,34 +352,85 @@ class AtomItem(QGraphicsItem):
287
352
  path.addEllipse(QPointF(0, 0), scene_radius, scene_radius)
288
353
  return path
289
354
 
355
+ # In class AtomItem:
356
+
290
357
  def paint(self, painter, option, widget):
291
358
  color = CPK_COLORS.get(self.symbol, CPK_COLORS['DEFAULT'])
292
359
  if self.is_visible:
293
- # 1. 原子記号(C, Nなど)を描画するための準備
360
+ # 1. 描画の準備
294
361
  painter.setFont(self.font)
295
362
  fm = painter.fontMetrics()
296
- text_rect = fm.boundingRect(self.symbol)
297
- text_rect.moveCenter(QPointF(0, 0).toPoint())
298
-
299
- # 2. 原子記号の背景を白で塗りつぶす(結合線と重なっても見やすくするため)
363
+
364
+ # --- 水素部分のテキストを作成 ---
365
+ hydrogen_part = ""
366
+ if self.implicit_h_count > 0:
367
+ is_skeletal_carbon = (self.symbol == 'C' and len(self.bonds) > 0)
368
+ if not is_skeletal_carbon:
369
+ hydrogen_part = "H"
370
+ if self.implicit_h_count > 1:
371
+ subscript_map = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
372
+ hydrogen_part += str(self.implicit_h_count).translate(subscript_map)
373
+
374
+ # --- テキストを反転させるか決定 ---
375
+ flip_text = False
376
+ # 水素ラベルがあり、結合が1本以上ある場合のみ反転を考慮
377
+ if hydrogen_part and self.bonds:
378
+
379
+ # 相対的なX座標で、結合が左右どちらに偏っているか判定
380
+ my_pos_x = self.pos().x()
381
+ total_dx = 0
382
+ for bond in self.bonds:
383
+ other_atom = bond.atom1 if bond.atom2 is self else bond.atom2
384
+ total_dx += (other_atom.pos().x() - my_pos_x)
385
+
386
+ # 結合が主に右側にある場合はテキストを反転させる
387
+ if total_dx > 0:
388
+ flip_text = True
389
+
390
+ # --- 表示テキストとアライメントを最終決定 ---
391
+ if flip_text:
392
+ display_text = hydrogen_part + self.symbol
393
+ alignment_flag = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
394
+ else:
395
+ display_text = self.symbol + hydrogen_part
396
+ alignment_flag = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
397
+
398
+ text_rect = fm.boundingRect(display_text)
399
+ text_rect.adjust(-2, -2, 2, 2)
400
+ symbol_rect = fm.boundingRect(self.symbol) # 主元素のみの幅を計算
401
+
402
+ # --- テキストの描画位置を決定 ---
403
+ # 水素ラベルがない場合 (従来通り中央揃え)
404
+ if not hydrogen_part:
405
+ alignment_flag = Qt.AlignmentFlag.AlignCenter
406
+ text_rect.moveCenter(QPointF(0, 0).toPoint())
407
+ # 水素ラベルがあり、反転する場合 (右揃え)
408
+ elif flip_text:
409
+ # 主元素の中心が原子の中心に来るように、矩形の右端を調整
410
+ offset_x = symbol_rect.width() // 2
411
+ text_rect.moveTo(offset_x - text_rect.width(), -text_rect.height() // 2)
412
+ # 水素ラベルがあり、反転しない場合 (左揃え)
413
+ else:
414
+ # 主元素の中心が原子の中心に来るように、矩形の左端を調整
415
+ offset_x = -symbol_rect.width() // 2
416
+ text_rect.moveTo(offset_x, -text_rect.height() // 2)
417
+
418
+ # 2. 原子記号の背景を白で塗りつぶす
300
419
  if self.scene():
301
420
  bg_brush = self.scene().backgroundBrush()
302
- bg_rect = text_rect.adjusted(-3, -3, 3, 3)
421
+ bg_rect = text_rect.adjusted(-5, -8, 5, 8)
303
422
  painter.setBrush(bg_brush)
304
423
  painter.setPen(Qt.PenStyle.NoPen)
305
- painter.drawRect(bg_rect)
424
+ painter.drawEllipse(bg_rect)
306
425
 
307
426
  # 3. 原子記号自体を描画
308
- # 水素だけ黒文字、それ以外は CPK カラー
309
427
  if self.symbol == 'H':
310
428
  painter.setPen(QPen(Qt.GlobalColor.black))
311
429
  else:
312
430
  painter.setPen(QPen(color))
313
- painter.drawText(text_rect, Qt.AlignmentFlag.AlignCenter, self.symbol)
314
-
431
+ painter.drawText(text_rect, int(alignment_flag), display_text)
315
432
 
316
- # --- ここから電荷とラジカルの描画 ---
317
- # 電荷の描画
433
+ # --- 電荷とラジカルの描画 ---
318
434
  if self.charge != 0:
319
435
  if self.charge == 1: charge_str = "+"
320
436
  elif self.charge == -1: charge_str = "-"
@@ -322,11 +438,14 @@ class AtomItem(QGraphicsItem):
322
438
  charge_font = QFont("Arial", 12, QFont.Weight.Bold)
323
439
  painter.setFont(charge_font)
324
440
  charge_rect = painter.fontMetrics().boundingRect(charge_str)
325
- charge_pos = QPointF(text_rect.right() + 2, text_rect.top() + charge_rect.height() - 2)
441
+ # 電荷の位置も反転に対応
442
+ if flip_text:
443
+ charge_pos = QPointF(text_rect.left() - charge_rect.width() -2, text_rect.top() + charge_rect.height() - 2)
444
+ else:
445
+ charge_pos = QPointF(text_rect.right() + 2, text_rect.top() + charge_rect.height() - 2)
326
446
  painter.setPen(Qt.GlobalColor.black)
327
447
  painter.drawText(charge_pos, charge_str)
328
448
 
329
- # ラジカルの描画
330
449
  if self.radical > 0:
331
450
  painter.setBrush(QBrush(Qt.GlobalColor.black))
332
451
  painter.setPen(Qt.PenStyle.NoPen)
@@ -337,20 +456,18 @@ class AtomItem(QGraphicsItem):
337
456
  painter.drawEllipse(QPointF(text_rect.center().x() - 5, radical_pos_y), 3, 3)
338
457
  painter.drawEllipse(QPointF(text_rect.center().x() + 5, radical_pos_y), 3, 3)
339
458
 
340
- # 選択時のハイライト
459
+
460
+ # --- 選択時のハイライトなど ---
341
461
  if self.has_problem:
342
- # 優先度1: 化学的な問題がある場合は赤枠で強制的にハイライト
343
462
  painter.setBrush(Qt.BrushStyle.NoBrush)
344
- painter.setPen(QPen(QColor(255, 0, 0, 200), 4)) # 赤色で太めの枠
463
+ painter.setPen(QPen(QColor(255, 0, 0, 200), 4))
345
464
  painter.drawRect(self.boundingRect())
346
-
347
465
  elif self.isSelected():
348
466
  painter.setBrush(Qt.BrushStyle.NoBrush)
349
467
  painter.setPen(QPen(QColor(0, 100, 255), 3))
350
468
  painter.drawRect(self.boundingRect())
351
-
352
469
  if (not self.isSelected()) and getattr(self, 'hovered', False):
353
- pen = QPen(QColor(144, 238, 144, 200), 3) # lightgreen, 少し透過
470
+ pen = QPen(QColor(144, 238, 144, 200), 3)
354
471
  pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
355
472
  painter.setBrush(Qt.BrushStyle.NoBrush)
356
473
  painter.setPen(pen)
@@ -402,7 +519,7 @@ class BondItem(QGraphicsItem):
402
519
  try: line = self.get_line_in_local_coords()
403
520
  except Exception: line = QLineF(0, 0, 0, 0)
404
521
  bond_offset = globals().get('BOND_OFFSET', 2)
405
- extra = (getattr(self, 'order', 1) - 1) * bond_offset + 15 # extraを拡大
522
+ extra = (getattr(self, 'order', 1) - 1) * bond_offset + 20 # extraを拡大
406
523
  return QRectF(line.p1(), line.p2()).normalized().adjusted(-extra, -extra, extra, extra)
407
524
 
408
525
  def shape(self):
@@ -439,13 +556,14 @@ class BondItem(QGraphicsItem):
439
556
  line = self.get_line_in_local_coords()
440
557
  if line.length() == 0: return
441
558
 
442
- # --- 1. 結合の種類に応じて本体を描画 ---
443
- current_pen = self.pen
559
+ # --- 1. 選択状態に応じてペンとブラシを準備 ---
444
560
  if self.isSelected():
445
- current_pen = QPen(QColor("blue"), 3)
446
-
447
- painter.setPen(current_pen)
448
- painter.setBrush(QBrush(Qt.GlobalColor.black))
561
+ selection_color = QColor("blue")
562
+ painter.setPen(QPen(selection_color, 3))
563
+ painter.setBrush(QBrush(selection_color))
564
+ else:
565
+ painter.setPen(self.pen)
566
+ painter.setBrush(QBrush(Qt.GlobalColor.black))
449
567
 
450
568
  # --- 立体化学 (Wedge/Dash) の描画 ---
451
569
  if self.order == 1 and self.stereo in [1, 2]:
@@ -455,16 +573,16 @@ class BondItem(QGraphicsItem):
455
573
  p2 = line.p2() - vec.p2() * 5
456
574
 
457
575
  if self.stereo == 1: # Wedge (くさび形)
458
- if self.isSelected():
459
- painter.setBrush(Qt.BrushStyle.NoBrush) # 選択時は塗りつぶさず枠線にする
460
576
  offset = QPointF(normal.dx(), normal.dy()) * 6.0
461
577
  poly = QPolygonF([p1, p2 + offset, p2 - offset])
462
578
  painter.drawPolygon(poly)
463
579
 
464
580
  elif self.stereo == 2: # Dash (破線)
465
- pen = painter.pen()
466
- pen.setWidthF(2.5) # デフォルトの太さ(2)から少し太くする
467
- painter.setPen(pen)
581
+ if not self.isSelected():
582
+ pen = painter.pen()
583
+ pen.setWidthF(2.5)
584
+ painter.setPen(pen)
585
+
468
586
  num_dashes = 8
469
587
  for i in range(num_dashes + 1):
470
588
  t = i / num_dashes
@@ -608,12 +726,14 @@ class MoleculeScene(QGraphicsScene):
608
726
  if isinstance(item, AtomItem):
609
727
  # ラジカルモードの場合、ラジカルを0にする
610
728
  if self.mode == 'radical' and item.radical != 0:
729
+ item.prepareGeometryChange()
611
730
  item.radical = 0
612
731
  self.data.atoms[item.atom_id]['radical'] = 0
613
732
  item.update_style()
614
733
  data_changed = True
615
734
  # 電荷モードの場合、電荷を0にする
616
735
  elif self.mode in ['charge_plus', 'charge_minus'] and item.charge != 0:
736
+ item.prepareGeometryChange()
617
737
  item.charge = 0
618
738
  self.data.atoms[item.atom_id]['charge'] = 0
619
739
  item.update_style()
@@ -719,6 +839,7 @@ class MoleculeScene(QGraphicsScene):
719
839
  # 1. 特殊モード(ラジカル/電荷)の処理
720
840
  if (self.mode == 'radical') and is_click and isinstance(released_item, AtomItem):
721
841
  atom = released_item
842
+ atom.prepareGeometryChange()
722
843
  # ラジカルの状態をトグル (0 -> 1 -> 2 -> 0)
723
844
  atom.radical = (atom.radical + 1) % 3
724
845
  self.data.atoms[atom.atom_id]['radical'] = atom.radical
@@ -726,6 +847,7 @@ class MoleculeScene(QGraphicsScene):
726
847
  self.data_changed_in_event = True
727
848
  elif (self.mode == 'charge_plus' or self.mode == 'charge_minus') and is_click and isinstance(released_item, AtomItem):
728
849
  atom = released_item
850
+ atom.prepareGeometryChange()
729
851
  delta = 1 if self.mode == 'charge_plus' else -1
730
852
  atom.charge += delta
731
853
  self.data.atoms[atom.atom_id]['charge'] = atom.charge
@@ -1268,6 +1390,7 @@ class MoleculeScene(QGraphicsScene):
1268
1390
  if target_atoms:
1269
1391
  for atom in target_atoms:
1270
1392
  # ラジカルの状態をトグル (0 -> 1 -> 2 -> 0)
1393
+ atom.prepareGeometryChange()
1271
1394
  atom.radical = (atom.radical + 1) % 3
1272
1395
  self.data.atoms[atom.atom_id]['radical'] = atom.radical
1273
1396
  atom.update_style()
@@ -1287,6 +1410,7 @@ class MoleculeScene(QGraphicsScene):
1287
1410
  if target_atoms:
1288
1411
  delta = 1 if key == Qt.Key.Key_Plus else -1
1289
1412
  for atom in target_atoms:
1413
+ atom.prepareGeometryChange()
1290
1414
  atom.charge += delta
1291
1415
  self.data.atoms[atom.atom_id]['charge'] = atom.charge
1292
1416
  atom.update_style()
@@ -1303,9 +1427,22 @@ class MoleculeScene(QGraphicsScene):
1303
1427
  new_symbol = self.key_to_symbol_map_shift[key]
1304
1428
 
1305
1429
  if new_symbol and item_at_cursor.symbol != new_symbol:
1430
+ item_at_cursor.prepareGeometryChange() # <<<<<< この行を追加
1431
+
1306
1432
  item_at_cursor.symbol = new_symbol
1307
1433
  self.data.atoms[item_at_cursor.atom_id]['symbol'] = new_symbol
1308
1434
  item_at_cursor.update_style()
1435
+
1436
+
1437
+ atoms_to_update = {item_at_cursor}
1438
+ for bond in item_at_cursor.bonds:
1439
+ bond.update()
1440
+ other_atom = bond.atom1 if bond.atom2 is item_at_cursor else bond.atom2
1441
+ atoms_to_update.add(other_atom)
1442
+
1443
+ for atom in atoms_to_update:
1444
+ atom.update_style()
1445
+
1309
1446
  self.window.push_undo_state()
1310
1447
  event.accept()
1311
1448
  return
@@ -2743,6 +2880,7 @@ class MainWindow(QMainWindow):
2743
2880
  self.set_mode('select')
2744
2881
  if 'select' in self.mode_actions:
2745
2882
  self.mode_actions['select'].setChecked(True)
2883
+
2746
2884
  def trigger_conversion(self):
2747
2885
  self.scene.clear_all_problem_flags()
2748
2886
  mol = self.data.to_rdkit_mol()
@@ -2943,6 +3081,7 @@ class MainWindow(QMainWindow):
2943
3081
  self.export_button.setEnabled(False)
2944
3082
  self.edit_3d_action.setEnabled(False)
2945
3083
 
3084
+ self.update_implicit_hydrogens()
2946
3085
  self.update_chiral_labels()
2947
3086
 
2948
3087
 
@@ -2970,7 +3109,8 @@ class MainWindow(QMainWindow):
2970
3109
  state = self.get_current_state()
2971
3110
  self.undo_stack.append(state)
2972
3111
  self.redo_stack.clear()
2973
-
3112
+
3113
+ self.update_implicit_hydrogens()
2974
3114
  self.update_realtime_info()
2975
3115
  self.update_undo_redo_actions()
2976
3116
 
@@ -3071,6 +3211,44 @@ class MainWindow(QMainWindow):
3071
3211
  if push_to_undo:
3072
3212
  self.push_undo_state()
3073
3213
 
3214
+ def update_implicit_hydrogens(self):
3215
+ """現在の2D構造に基づいて各原子の暗黙の水素数を計算し、AtomItemに反映する"""
3216
+ if not self.data.atoms:
3217
+ return
3218
+
3219
+ try:
3220
+ mol = self.data.to_rdkit_mol()
3221
+ if mol is None:
3222
+ # 構造が不正な場合、全原子の水素カウントを0に戻して再描画
3223
+ for atom_data in self.data.atoms.values():
3224
+ if atom_data.get('item') and atom_data['item'].implicit_h_count != 0:
3225
+ atom_data['item'].implicit_h_count = 0
3226
+ atom_data['item'].update()
3227
+ return
3228
+
3229
+ mol.UpdatePropertyCache(strict=False)
3230
+
3231
+ items_to_update = []
3232
+ for atom in mol.GetAtoms():
3233
+ if atom.HasProp("_original_atom_id"):
3234
+ original_id = atom.GetIntProp("_original_atom_id")
3235
+ if original_id in self.data.atoms:
3236
+ item = self.data.atoms[original_id].get('item')
3237
+ if item:
3238
+ h_count = atom.GetNumImplicitHs()
3239
+ if item.implicit_h_count != h_count:
3240
+ item.prepareGeometryChange()
3241
+ item.implicit_h_count = h_count
3242
+ items_to_update.append(item)
3243
+
3244
+ # カウントが変更されたアイテムのみ再描画をトリガー
3245
+ for item in items_to_update:
3246
+ item.update()
3247
+ except Exception:
3248
+ # 編集中に一時的に発生するエラーなどで計算が失敗してもアプリは継続
3249
+ pass
3250
+
3251
+
3074
3252
  def import_smiles_dialog(self):
3075
3253
  """ユーザーにSMILES文字列の入力を促すダイアログを表示する"""
3076
3254
  smiles, ok = QInputDialog.getText(self, "Import SMILES", "Enter SMILES string:")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moleditpy
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
5
5
  Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/HiroYokoyama/python_molecular_editor
File without changes
File without changes