001/*
002 * (C) Copyright 2006-2020 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Tiago Cardoso <tcardoso@nuxeo.com>
018 */
019package org.nuxeo.ecm.platform.threed.service;
020
021import static org.nuxeo.ecm.platform.threed.ThreeDDocumentConstants.RENDER_VIEWS_PROPERTY;
022import static org.nuxeo.ecm.platform.threed.ThreeDDocumentConstants.TRANSMISSIONS_PROPERTY;
023import static org.nuxeo.ecm.platform.threed.convert.Constants.BATCH_CONVERTER;
024import static org.nuxeo.ecm.platform.threed.convert.Constants.COLLADA2GLTF_CONVERTER;
025import static org.nuxeo.ecm.platform.threed.convert.Constants.COORDS_PARAMETER;
026import static org.nuxeo.ecm.platform.threed.convert.Constants.DIMENSIONS_PARAMETER;
027import static org.nuxeo.ecm.platform.threed.convert.Constants.LOD_IDS_PARAMETER;
028import static org.nuxeo.ecm.platform.threed.convert.Constants.MAX_POLY_PARAMETER;
029import static org.nuxeo.ecm.platform.threed.convert.Constants.OPERATORS_PARAMETER;
030import static org.nuxeo.ecm.platform.threed.convert.Constants.PERC_POLY_PARAMETER;
031import static org.nuxeo.ecm.platform.threed.convert.Constants.RENDER_IDS_PARAMETER;
032
033import java.io.Serializable;
034import java.util.ArrayList;
035import java.util.Collection;
036import java.util.HashMap;
037import java.util.List;
038import java.util.Map;
039import java.util.concurrent.TimeUnit;
040import java.util.stream.Collectors;
041
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.nuxeo.ecm.core.api.Blob;
045import org.nuxeo.ecm.core.api.DocumentModel;
046import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
047import org.nuxeo.ecm.core.api.blobholder.SimpleBlobHolder;
048import org.nuxeo.ecm.core.convert.api.ConversionService;
049import org.nuxeo.ecm.core.work.api.Work;
050import org.nuxeo.ecm.core.work.api.Work.State;
051import org.nuxeo.ecm.core.work.api.WorkManager;
052import org.nuxeo.ecm.platform.threed.ThreeD;
053import org.nuxeo.ecm.platform.threed.ThreeDBatchProgress;
054import org.nuxeo.ecm.platform.threed.TransmissionThreeD;
055import org.nuxeo.runtime.api.Framework;
056import org.nuxeo.runtime.model.ComponentContext;
057import org.nuxeo.runtime.model.ComponentInstance;
058import org.nuxeo.runtime.model.DefaultComponent;
059
060/**
061 * Default implementation of {@link ThreeDService}
062 *
063 * @since 8.4
064 */
065public class ThreeDServiceImpl extends DefaultComponent implements ThreeDService {
066
067    protected static final Log log = LogFactory.getLog(ThreeDServiceImpl.class);
068
069    public static final String RENDER_VIEWS_EP = "renderViews";
070
071    public static final String DEFAULT_RENDER_VIEWS_EP = "automaticRenderViews";
072
073    public static final String DEFAULT_LODS_EP = "automaticLOD";
074
075    protected AutomaticLODContributionHandler automaticLODs;
076
077    protected AutomaticRenderViewContributionHandler automaticRenderViews;
078
079    protected RenderViewContributionHandler renderViews;
080
081    @Override
082    public void activate(ComponentContext context) {
083        automaticLODs = new AutomaticLODContributionHandler();
084        automaticRenderViews = new AutomaticRenderViewContributionHandler();
085        renderViews = new RenderViewContributionHandler();
086    }
087
088    @Override
089    public void deactivate(ComponentContext context) {
090        WorkManager workManager = Framework.getService(WorkManager.class);
091        if (workManager != null && workManager.isStarted()) {
092            try {
093                workManager.shutdownQueue(
094                        workManager.getCategoryQueueId(ThreeDBatchUpdateWork.CATEGORY_THREED_CONVERSION), 10,
095                        TimeUnit.SECONDS);
096            } catch (InterruptedException e) {
097                Thread.currentThread().interrupt();
098                throw new RuntimeException(e);
099            }
100        }
101        automaticLODs = null;
102        automaticRenderViews = null;
103        renderViews = null;
104    }
105
106    @Override
107    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
108        switch (extensionPoint) {
109        case RENDER_VIEWS_EP:
110            renderViews.addContribution((RenderView) contribution);
111            break;
112        case DEFAULT_RENDER_VIEWS_EP:
113            automaticRenderViews.addContribution((AutomaticRenderView) contribution);
114            break;
115        case DEFAULT_LODS_EP:
116            automaticLODs.addContribution((AutomaticLOD) contribution);
117        }
118    }
119
120    @Override
121    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
122        switch (extensionPoint) {
123        case RENDER_VIEWS_EP:
124            renderViews.removeContribution((RenderView) contribution);
125            break;
126        case DEFAULT_RENDER_VIEWS_EP:
127            automaticRenderViews.removeContribution((AutomaticRenderView) contribution);
128            break;
129        case DEFAULT_LODS_EP:
130            automaticLODs.removeContribution((AutomaticLOD) contribution);
131        }
132    }
133
134    @Override
135    public void cleanBatchData(DocumentModel doc) {
136        List<Map<String, Serializable>> emptyList = new ArrayList<>();
137        doc.setPropertyValue(TRANSMISSIONS_PROPERTY, (Serializable) emptyList);
138        doc.setPropertyValue(RENDER_VIEWS_PROPERTY, (Serializable) emptyList);
139    }
140
141    @Override
142    public void launchBatchConversion(DocumentModel doc) {
143        cleanBatchData(doc);
144        ThreeDBatchUpdateWork work = new ThreeDBatchUpdateWork(doc.getRepositoryName(), doc.getId());
145        WorkManager workManager = Framework.getService(WorkManager.class);
146        workManager.schedule(work, WorkManager.Scheduling.IF_NOT_SCHEDULED, true);
147    }
148
149    @Override
150    public BlobHolder batchConvert(ThreeD originalThreed) {
151        ConversionService cs = Framework.getService(ConversionService.class);
152        // get all the 3d content blobs
153        List<Blob> in = new ArrayList<>();
154        in.add(originalThreed.getBlob());
155        if (originalThreed.getResources() != null) {
156            in.addAll(originalThreed.getResources());
157        }
158
159        // gather 3D contribution default contributions
160        List<RenderView> renderViews = (List<RenderView>) getAutomaticRenderViews();
161        List<AutomaticLOD> lods = (List<AutomaticLOD>) getAutomaticLODs();
162
163        // setup all work to be done in batch process (renders, lods)
164        Map<String, Serializable> params = new HashMap<>();
165
166        // operators
167        String operators = "import info";
168        // add renders
169        operators += new String(new char[renderViews.size()]).replace("\0", " render");
170        // add lods
171        operators += new String(new char[lods.size()]).replace("\0", " lod info convert");
172        params.put(OPERATORS_PARAMETER, operators);
173
174        // render ids
175        params.put(RENDER_IDS_PARAMETER, renderViews.stream().map(RenderView::getId).collect(Collectors.joining(" ")));
176
177        // lod ids
178        params.put(LOD_IDS_PARAMETER, lods.stream().map(AutomaticLOD::getId).collect(Collectors.joining(" ")));
179
180        // percPoly
181        params.put(PERC_POLY_PARAMETER,
182                lods.stream().map(AutomaticLOD::getPercPoly).map(String::valueOf).collect(Collectors.joining(" ")));
183
184        // maxPoly
185        params.put(MAX_POLY_PARAMETER,
186                lods.stream().map(AutomaticLOD::getMaxPoly).map(String::valueOf).collect(Collectors.joining(" ")));
187
188        params.put(COORDS_PARAMETER,
189                renderViews.stream().map(renderView -> renderView.getAzimuth() + "," + renderView.getZenith()).collect(
190                        Collectors.joining(" ")));
191
192        // dimensions
193        params.put(DIMENSIONS_PARAMETER,
194                renderViews.stream().map(renderView -> renderView.getWidth() + "x" + renderView.getHeight()).collect(
195                        Collectors.joining(" ")));
196
197        return cs.convert(BATCH_CONVERTER, new SimpleBlobHolder(in), params);
198    }
199
200    @Override
201    public Collection<RenderView> getAvailableRenderViews() {
202        return renderViews.registry.values();
203    }
204
205    @Override
206    public Collection<RenderView> getAutomaticRenderViews() {
207        return automaticRenderViews.registry.values()
208                                            .stream()
209                                            .filter(AutomaticRenderView::isEnabled)
210                                            .sorted((o1, o2) -> o1.getOrder() - o2.getOrder())
211                                            .map(AutomaticRenderView::getId)
212                                            .map(this::getRenderView)
213                                            .filter(RenderView::isEnabled)
214                                            .collect(Collectors.toList());
215    }
216
217    @Override
218    public Collection<AutomaticLOD> getAvailableLODs() {
219        return automaticLODs.registry.values();
220    }
221
222    @Override
223    public Collection<AutomaticLOD> getAutomaticLODs() {
224        return automaticLODs.registry.values()
225                                     .stream()
226                                     .filter(AutomaticLOD::isEnabled)
227                                     .sorted((o1, o2) -> o1.getOrder() - o2.getOrder())
228                                     .collect(Collectors.toList());
229    }
230
231    @Override
232    public AutomaticLOD getAutomaticLOD(String automaticLODId) {
233        return automaticLODs.registry.get(automaticLODId);
234    }
235
236    @Override
237    public RenderView getRenderView(String renderViewId) {
238        return renderViews.registry.get(renderViewId);
239    }
240
241    @Override
242    public RenderView getRenderView(Integer azimuth, Integer zenith) {
243        return renderViews.registry.values()
244                                   .stream()
245                                   .filter(renderView -> renderView.getAzimuth().equals(azimuth)
246                                           && renderView.getZenith().equals(zenith))
247                                   .findFirst()
248                                   .orElse(null);
249    }
250
251    @Override
252    public TransmissionThreeD convertColladaToglTF(TransmissionThreeD colladaThreeD) {
253        ConversionService cs = Framework.getService(ConversionService.class);
254        Map<String, Serializable> parameters = new HashMap<>();
255        List<Blob> blobs = new ArrayList<>();
256        blobs.add(colladaThreeD.getBlob());
257        if (colladaThreeD.getResources() != null) {
258            blobs.addAll(colladaThreeD.getResources());
259        }
260        BlobHolder result = cs.convert(COLLADA2GLTF_CONVERTER, new SimpleBlobHolder(blobs), parameters);
261        return new TransmissionThreeD(result.getBlobs().get(0), null, colladaThreeD.getInfo(),
262                colladaThreeD.getPercPoly(), colladaThreeD.getMaxPoly(), colladaThreeD.getPercTex(),
263                colladaThreeD.getMaxTex(), colladaThreeD.getName());
264    }
265
266    @Override
267    public ThreeDBatchProgress getBatchProgress(String repositoryName, String docId) {
268        WorkManager workManager = Framework.getService(WorkManager.class);
269        Work work = new ThreeDBatchUpdateWork(repositoryName, docId);
270        Work workRunning = workManager.find(work.getId(), State.RUNNING);
271        if (workRunning != null) {
272            return new ThreeDBatchProgress(ThreeDBatchProgress.STATUS_CONVERSION_RUNNING, workRunning.getStatus());
273        }
274        Work workScheduled = workManager.find(work.getId(), State.SCHEDULED);
275        if (workScheduled != null) {
276            return new ThreeDBatchProgress(ThreeDBatchProgress.STATUS_CONVERSION_QUEUED, "");
277        }
278        return new ThreeDBatchProgress(ThreeDBatchProgress.STATUS_CONVERSION_UNKNOWN, "");
279    }
280}