Skip to content

Commit 691243f

Browse files
committed
pyluxcoretools: big refactoring
- replace PySide by tkinter (bundled in Python) - add __main__ everywhere it was lacking - add __init__ everywhere it was lacking - remove redundant prefixes 'pyluxcore...' in submodule names - lint (pylint) - move to `python` folder, along with other Python modules - declare pyluxcoretools submodules as scripts in the wheel
1 parent 20cacf2 commit 691243f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+4010
-5392
lines changed

CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,6 @@ add_subdirectory(src/luxrays)
305305
add_subdirectory(src/slg)
306306
add_subdirectory(src/luxcore)
307307
add_subdirectory(src/pyluxcore)
308-
add_subdirectory(src/pyluxcoretools)
309308

310309
################################################################################
311310
#

pyproject.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,24 @@ GitHub = "https://github.com/LuxCoreRender/LuxCore"
4646

4747
[project.scripts]
4848
pyluxcoretest = "pyluxcoretest:main"
49+
pyluxcore-console = "pyluxcoretools.console.cmd:main"
50+
pyluxcore-maketx = "pyluxcoretools.maketx.cmd:main"
51+
pyluxcore-merge = "pyluxcoretools.merge.cmd:main"
52+
pyluxcore-netmenu = "pyluxcoretools.netmenu.cmd:main"
53+
pyluxcore-netconsole = "pyluxcoretools.netconsole.cmd:main"
54+
pyluxcore-netnode = "pyluxcoretools.netnode.cmd:main"
55+
56+
[project.gui-scripts]
57+
pyluxcore-netconsole-ui = "pyluxcoretools.netconsole.ui:main"
58+
pyluxcore-netnode-ui = "pyluxcoretools.netnode.ui:main"
4959

5060
[tool.scikit-build]
5161
logging.level = "DEBUG"
5262
build.verbose = true
5363
cmake.source-dir = "."
5464
build.targets = ["pyluxcore"]
5565
wheel.cmake = true
56-
wheel.packages = ["python/pyluxcore", "python/pyluxcoretest"]
66+
wheel.packages = ["python/pyluxcore", "python/pyluxcoretest", "python/pyluxcoretools"]
5767
build-dir = "out"
5868
ninja.make-fallback = false
5969
install.components = ["pyluxcore"]

python/pyluxcoretools/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
###############################################################################
2+
# Copyright 1998-2025 by authors (see AUTHORS.txt)
3+
#
4+
# This file is part of LuxCoreRender.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
###############################################################################
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
###############################################################################
2+
# Copyright 1998-2025 by authors (see AUTHORS.txt)
3+
#
4+
# This file is part of LuxCoreRender.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
###############################################################################
18+
19+
from .renderfarm import DEFAULT_PORT, RenderFarm, NodeDiscoveryType
20+
from .node import RenderFarmNode
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
###############################################################################
2+
# Copyright 1998-2025 by authors (see AUTHORS.txt)
3+
#
4+
# This file is part of LuxCoreRender.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
###############################################################################
18+
19+
import os
20+
import time
21+
import logging
22+
import threading
23+
import functools
24+
25+
import pyluxcore
26+
from .._utils import loghandler
27+
28+
logger = logging.getLogger(loghandler.loggerName + "._renderfarm")
29+
30+
31+
class RenderFarmFilmMerger:
32+
def __init__(self, renderFarmJob):
33+
self.renderFarmJob = renderFarmJob
34+
self.filmMergeThread = None
35+
self.filmMergeThreadEvent = threading.Event()
36+
self.previousFilmSampleCount = 0
37+
self.filmMergeThreadEventForceFilmMerge = None
38+
self.filmMergeThreadEventForceFilmMergePeriod = None
39+
self.filmMergeThreadEventStop = None
40+
41+
# -------------------------------------------------------------------------
42+
# Start/Stop the work
43+
# -------------------------------------------------------------------------
44+
45+
def Start(self):
46+
# Start the film merge thread
47+
self.filmMergeThread = threading.Thread(
48+
target=functools.partial(
49+
RenderFarmFilmMerger.__FilmMergeThread, self
50+
)
51+
)
52+
self.filmMergeThread.name = "FilmMergeThread"
53+
54+
self.filmMergeThreadEvent.clear()
55+
self.filmMergeThreadEventStop = False
56+
self.filmMergeThreadEventForceFilmMerge = False
57+
self.filmMergeThreadEventForceFilmMergePeriod = False
58+
59+
self.filmMergeThread.start()
60+
61+
def Stop(self):
62+
# Stop the merge film thread
63+
self.filmMergeThreadEventStop = True
64+
self.filmMergeThreadEvent.set()
65+
self.filmMergeThread.join()
66+
67+
def ForceFilmMerge(self):
68+
self.filmMergeThreadEventForceFilmMerge = True
69+
self.filmMergeThreadEvent.set()
70+
71+
def ForceFilmMergePeriod(self):
72+
self.filmMergeThreadEventForceFilmMergePeriod = True
73+
self.filmMergeThreadEvent.set()
74+
75+
# -----------------------------------------------------------------------
76+
# Film merge related methods
77+
# -----------------------------------------------------------------------
78+
79+
def MergeAllFilms(self):
80+
# Merge all NodeThreadFilms
81+
film = None
82+
83+
# Check if I have a previous film to use
84+
if self.renderFarmJob.previousFilmFileName:
85+
logger.info(
86+
"Loaded previous film: %s",
87+
self.renderFarmJob.previousFilmFileName,
88+
)
89+
film = pyluxcore.Film(self.renderFarmJob.previousFilmFileName)
90+
91+
stats = film.GetStats()
92+
self.previousFilmSampleCount = stats.Get(
93+
"stats.film.total.samplecount"
94+
).GetFloat()
95+
else:
96+
self.previousFilmSampleCount = 0
97+
logger.info("No previous film to load")
98+
99+
# Get a copy of nodeThreads in order to be thread safe
100+
nodeThreadsCopy = self.renderFarmJob.GetNodeThreadsList()
101+
102+
logger.info("Number of films to merge: %s", str(len(nodeThreadsCopy)))
103+
for nodeThread in nodeThreadsCopy:
104+
with nodeThread.lock:
105+
filmThreadFileName = nodeThread.GetNodeFilmFileName()
106+
# Check if the file exist
107+
if not os.path.isfile(filmThreadFileName):
108+
logger.info(
109+
"Film file not yet available: %s (%s)",
110+
nodeThread.thread.nam,
111+
filmThreadFileName,
112+
)
113+
continue
114+
115+
logger.info(
116+
"Merging film: %s (%s)",
117+
nodeThread.thread.name,
118+
filmThreadFileName,
119+
)
120+
if filmThread := pyluxcore.Film(filmThreadFileName):
121+
stats = filmThread.GetStats()
122+
spp = stats.Get("stats.film.spp").GetFloat()
123+
logger.info(" Samples per pixel: %.1f", spp)
124+
125+
if film:
126+
# Merge the film
127+
film.AddFilm(filmThread)
128+
else:
129+
# Read the first film
130+
film = filmThread
131+
132+
return film
133+
134+
def SaveMergedFilm(self, film):
135+
# Save the merged film
136+
filmName = self.renderFarmJob.GetFilmFileName()
137+
logger.info("Saving merged film: %s", filmName)
138+
# TODO add SafeSave
139+
film.SaveFilm(filmName)
140+
141+
# Save the image output
142+
imageName = self.renderFarmJob.GetImageFileName()
143+
logger.info("Saving merged image output: %s", imageName)
144+
film.SaveOutput(
145+
imageName,
146+
pyluxcore.FilmOutputType.RGB_IMAGEPIPELINE,
147+
pyluxcore.Properties(),
148+
)
149+
150+
# Get the halt conditions
151+
filmHaltSPP = self.renderFarmJob.GetFilmHaltSPP()
152+
filmHaltTime = self.renderFarmJob.GetFilmHaltTime()
153+
154+
# Print some film statistics
155+
stats = film.GetStats()
156+
logger.info("Merged film statistics:")
157+
spp = stats.Get("stats.film.spp").GetFloat()
158+
logger.info(" Samples per pixel: %.1f / %s", spp, str(filmHaltSPP))
159+
self.renderFarmJob.SetSamplesPixel(spp)
160+
161+
dt = time.time() - self.renderFarmJob.GetStartTime()
162+
logger.info(
163+
" Rendering time: %s/%s",
164+
time.strftime("%H:%M:%S", time.gmtime(dt)),
165+
time.strftime("%H:%M:%S", time.gmtime(filmHaltTime)),
166+
)
167+
168+
totalSamples = stats.Get("stats.film.total.samplecount").GetFloat()
169+
samplesSec = (
170+
0.0
171+
if dt <= 0.0
172+
else (totalSamples - self.previousFilmSampleCount) / dt
173+
)
174+
logger.info(
175+
" Samples/sec: %.1fM samples/sec", (samplesSec / 1000000.0)
176+
)
177+
self.renderFarmJob.SetSamplesSec(samplesSec)
178+
179+
return (spp > filmHaltSPP > 0) or (dt > filmHaltTime > 0)
180+
181+
def __FilmMergeThread(self):
182+
logger.info("Film merge thread started")
183+
184+
while True:
185+
doMerge = False
186+
if not self.filmMergeThreadEvent.wait(
187+
self.renderFarmJob.filmUpdatePeriod
188+
):
189+
# Time out, time to do a merge
190+
doMerge = True
191+
self.filmMergeThreadEvent.clear()
192+
193+
if self.filmMergeThreadEventStop:
194+
break
195+
196+
if self.filmMergeThreadEventForceFilmMergePeriod:
197+
self.filmMergeThreadEventForceFilmMergePeriod = False
198+
doMerge = True
199+
200+
if self.filmMergeThreadEventForceFilmMerge:
201+
self.filmMergeThreadEventForceFilmMerge = False
202+
doMerge = True
203+
204+
if len(self.renderFarmJob.GetNodeThreadsList()) == 0:
205+
continue
206+
207+
if doMerge:
208+
logger.info("Merging node films")
209+
210+
# Merge all NodeThreadFilms
211+
if film := self.MergeAllFilms():
212+
# Save the merged film
213+
if self.SaveMergedFilm(film):
214+
self.renderFarmJob.Stop(
215+
stopFilmMerger=False, lastUpdate=True
216+
)
217+
self.renderFarmJob.renderFarm.CurrentJobDone()
218+
break
219+
220+
logger.info("Film merge thread done")

0 commit comments

Comments
 (0)