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