Skip to content

Commit 1b4ef45

Browse files
Outline plugin version 0.2
Adds ability to save to file and generate them at different sizes.
1 parent fd57af9 commit 1b4ef45

File tree

1 file changed

+78
-10
lines changed

1 file changed

+78
-10
lines changed

plugins/outline.py

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@
1111
from pymol.Qt import QtGui
1212
from pymol.Qt import QtWidgets
1313

14+
from dataclasses import dataclass
15+
1416
import os
1517
from io import BytesIO
18+
from pathlib import Path
1619

1720
from PIL import Image
1821
from PIL import ImageChops
1922
from PIL import ImageDraw
2023
from PIL import ImageFilter
2124
from PIL import ImageOps
2225

23-
__version__ = "0.1"
26+
__version__ = "0.2"
2427

2528

2629
def __init_plugin__(app=None) -> None:
@@ -100,23 +103,33 @@ def _replace_color(img: Image, target_color: tuple,
100103
return edges
101104

102105

106+
@dataclass
107+
class Extent2D:
108+
width: int = 0
109+
height: int = 0
110+
111+
103112
def _outline(outline_sele: str, outline_color: tuple, outline_width: int,
104-
scale: int, reps: tuple) -> None:
113+
scale: int, reps: tuple, image_extent: Extent2D,
114+
save_file: bool) -> None:
105115
"""
106116
Outline a selection's representations with a specific color.
107117
:param outline_sele: Selection to outline
108118
:param outline_color: Color to outline with
109119
:param outline_width: Width of outline
110120
:param scale: Scale factor for antialiasing
111121
:param reps: Representations to outline
122+
:param image_extent: Image extent
123+
:param save_file: Should save to file
112124
"""
113125
try:
114126
tmp_scene = "tmp_scene"
115127

116128
cmd.scene(tmp_scene, "store", quiet=1)
117129

118130
# Render what we have
119-
base_bytes = cmd.png(filename=None, ray=1)
131+
width, height = image_extent.width, image_extent.height
132+
base_bytes = cmd.png(filename=None, ray=1, width=width, height=height)
120133

121134
# Render only whats outlined
122135
cmd.hide('everything')
@@ -138,7 +151,6 @@ def _outline(outline_sele: str, outline_color: tuple, outline_width: int,
138151

139152
ray_antialias = cmd.get('antialias')
140153
cmd.set('antialias', 0)
141-
(width, height) = cmd.get_viewport()
142154
overlay_bytes = cmd.png(filename=None, ray=1, width=width*scale,
143155
height=height*scale)
144156

@@ -149,10 +161,26 @@ def _outline(outline_sele: str, outline_color: tuple, outline_width: int,
149161

150162
composite = Image.composite(overlay, base, overlay)
151163

164+
composition_file = Path.cwd() / "_tmp_outline_comp.png"
165+
composition_file_name = str(composition_file)
166+
composite.save(composition_file_name)
152167
# TODO: load_png doesn't take raw bytes so we have to save to disk
153-
tmp_composite_png = "_tmp_outline_comp.png"
154-
composite.save(tmp_composite_png)
155-
cmd.load_png(tmp_composite_png, quiet=1)
168+
cmd.load_png(composition_file_name, quiet=1)
169+
if save_file:
170+
new_name = QtWidgets.QFileDialog.getSaveFileName(
171+
None, "Save File", str(Path.cwd()), "PNG Files (*.png)")[0]
172+
# rename file
173+
new_path = Path(new_name)
174+
if new_path.exists():
175+
new_path.unlink()
176+
composition_file.rename(new_path)
177+
composition_file_name = str(new_path)
178+
msg = QtWidgets.QMessageBox()
179+
msg.setWindowTitle("File Saved")
180+
msg.setText(f"Saved to {composition_file_name}")
181+
msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
182+
msg.exec_()
183+
156184
finally:
157185
# Revert scene and clean up
158186
cmd.scene(tmp_scene, "recall", quiet=1)
@@ -162,7 +190,8 @@ def _outline(outline_sele: str, outline_color: tuple, outline_width: int,
162190
cmd.set('ray_trace_color', ray_trace_color)
163191
cmd.set('bg_rgb', bg_color)
164192
cmd.set('ray_opaque_background', ray_opaque_background)
165-
os.remove(tmp_composite_png)
193+
if not save_file:
194+
composition_file.unlink()
166195

167196

168197
class StringListSelectorWidgetItem(QtWidgets.QWidget):
@@ -251,6 +280,34 @@ def addButton(self, label: str) -> QtWidgets.QRadioButton:
251280
return button
252281

253282

283+
class ImageExtentSelectionGroup(QtWidgets.QWidget):
284+
"""
285+
Two spinboxes for selecting image width and height
286+
"""
287+
def __init__(self, *args, **kwargs) -> None:
288+
super().__init__(*args, **kwargs)
289+
width, height = cmd.get_viewport()
290+
self.width = width
291+
self.height = height
292+
self.width_box = QtWidgets.QSpinBox()
293+
self.height_box = QtWidgets.QSpinBox()
294+
self.width_box.setRange(32, 8192)
295+
self.height_box.setRange(32, 8192)
296+
self.width_box.setValue(self.width)
297+
self.height_box.setValue(self.height)
298+
self.layout = QtWidgets.QHBoxLayout(self)
299+
self.layout.addWidget(QtWidgets.QLabel("Img Width:"))
300+
self.layout.addWidget(self.width_box)
301+
self.layout.addWidget(QtWidgets.QLabel("Img Height:"))
302+
self.layout.addWidget(self.height_box)
303+
304+
def get_extent(self) -> Extent2D:
305+
"""
306+
:return: Image extent
307+
"""
308+
return Extent2D(self.width_box.value(), self.height_box.value())
309+
310+
254311
class RepresentationOutlineDialog(QtWidgets.QDialog):
255312
"""
256313
Representation Outline Dialog that allows the user to outline a selection's
@@ -322,6 +379,12 @@ def __init__(self, *args, **kwargs) -> None:
322379
self.antialias_layout.addWidget(self.low_aa)
323380
self.antialias_layout.addWidget(self.hi_aa)
324381

382+
# Image Extent Spinbox group
383+
self.image_extent_selection_group = ImageExtentSelectionGroup()
384+
385+
# File save field
386+
self.save_file_checkbox = QtWidgets.QCheckBox("Save to file")
387+
325388
self._updateCol()
326389

327390
# Brief note
@@ -337,6 +400,8 @@ def __init__(self, *args, **kwargs) -> None:
337400
self.layout.addWidget(self.color_dialogue_btn)
338401
self.layout.addLayout(self.slider_layout)
339402
self.layout.addLayout(self.antialias_layout)
403+
self.layout.addWidget(self.image_extent_selection_group)
404+
self.layout.addWidget(self.save_file_checkbox)
340405
self.layout.addWidget(self.outline_button)
341406
self.layout.addWidget(self.note)
342407

@@ -381,9 +446,12 @@ def onComboChanged():
381446
def onOutlineClicked():
382447
col = self.color_dialogue.currentColor().getRgb()
383448
scale = int(self.antialias_group.checkedButton().text()[0])
384-
width = self._kernelToWidth(self.width_slider.value() * scale)
449+
outline_width = self._kernelToWidth(self.width_slider.value() * scale)
450+
image_extent = self.image_extent_selection_group.get_extent()
385451
_outline(self.combobox.currentText(), col,
386-
width, scale, self.rep_list.get_rep_list())
452+
outline_width, scale, self.rep_list.get_rep_list(),
453+
image_extent,
454+
self.save_file_checkbox.isChecked())
387455

388456
self.outline_button.clicked.connect(onOutlineClicked)
389457

0 commit comments

Comments
 (0)