<commit_before>#include "VLCThumbnailer.h"

#include <cstring>
#include <jpeglib.h>
#include <setjmp.h>

#include "IFile.h"
#include "logging/Logger.h"

VLCThumbnailer::VLCThumbnailer(const VLC::Instance &vlc)
    : m_instance( vlc )
    , m_snapshotRequired( false )
    , m_height( 0 )
{
}

bool VLCThumbnailer::initialize(IMetadataServiceCb *callback, IMediaLibrary *ml)
{
    m_cb = callback;
    m_ml = ml;
    return true;
}

unsigned int VLCThumbnailer::priority() const
{
    // This needs to be lower than the VLCMetadataService, since we want to know the file type.
    return 50;
}

bool VLCThumbnailer::run( FilePtr file, void *data )
{
    if ( file->type() == IFile::Type::UnknownType )
    {
        // If we don't know the file type yet, it actually looks more like a bug
        // since this should run after file type deduction, and not run in case
        // that step fails.
        m_cb->done( file, StatusError, data );
        return false;
    }
    else if ( file->type() != IFile::Type::VideoType )
    {
        // There's no point in generating a thumbnail for a non-video file.
        m_cb->done( file, StatusSuccess, data );
        return true;
    }

    VLC::Media media( m_instance, file->mrl(), VLC::Media::FromPath );
    media.addOption( ":no-audio" );
    VLC::MediaPlayer mp( media );

    setupVout( mp );

    if ( startPlayback( file, mp, data ) == false )
        return false;

    // Seek ahead to have a significant preview
    if ( seekAhead( file, mp, data ) == false )
        return false;

    if ( takeSnapshot( file, mp, data ) == false )
        return false;
    return true;
}

bool VLCThumbnailer::startPlayback( FilePtr file, VLC::MediaPlayer &mp, void* data )
{
    bool failed = true;

    std::unique_lock<std::mutex> lock( m_mutex );

    mp.eventManager().onPlaying([this, &failed]() {
        std::unique_lock<std::mutex> lock( m_mutex );
        failed = false;
        m_cond.notify_all();
    });
    mp.eventManager().onEncounteredError([this]() {
        std::unique_lock<std::mutex> lock( m_mutex );
        m_cond.notify_all();
    });
    mp.play();
    bool success = m_cond.wait_for( lock, std::chrono::seconds( 3 ), [&mp]() {
        return mp.state() == libvlc_Playing || mp.state() == libvlc_Error;
    });
    if ( success == false || failed == true )
    {
        // In case of timeout or error, don't go any further
        m_cb->done( file, StatusError, data );
        return false;
    }
    return true;
}

bool VLCThumbnailer::seekAhead( FilePtr file, VLC::MediaPlayer& mp, void* data )
{
    std::unique_lock<std::mutex> lock( m_mutex );
    float pos = .0f;
    auto event = mp.eventManager().onPositionChanged([this, &pos](float p) {
        std::unique_lock<std::mutex> lock( m_mutex );
        pos = p;
        m_cond.notify_all();
    });
    mp.setPosition( .4f );
    bool success = m_cond.wait_for( lock, std::chrono::seconds( 3 ), [&pos]() {
        return pos >= .1f;
    });
    // Since we're locking a mutex for each position changed, let's unregister ASAP
    event->unregister();
    if ( success == false )
    {
        m_cb->done( file, StatusError, data );
        return false;
    }
    return true;
}

void VLCThumbnailer::setupVout( VLC::MediaPlayer& mp )
{
    mp.setVideoFormatCallbacks(
        // Setup
        [this, &mp](char* chroma, unsigned int* width, unsigned int *height, unsigned int *pitches, unsigned int *lines) {
            strcpy( chroma, "RV24" );

            const float inputAR = (float)*width / *height;

            *width = Width;
            auto prevHeight = m_height;
            m_height = (float)Width / inputAR + 1;
            // If our buffer isn't enough anymore, reallocate a new one.
            if ( m_height > prevHeight )
                m_buff.reset( new uint8_t[Width * m_height * Bpp] );
            *height = m_height;
            *pitches = Width * Bpp;
            *lines = m_height;
            return 1;
        },
        // Cleanup
        nullptr);
    mp.setVideoCallbacks(
        // Lock
        [this](void** pp_buff) {
            *pp_buff = m_buff.get();
            return nullptr;
        },
        //unlock
        [this](void*, void*const*) {
            std::unique_lock<std::mutex> lock(m_mutex);
            if ( m_snapshotRequired == true )
            {
                m_snapshotRequired = false;
                m_cond.notify_all();
            }
        }
        ,
        //display
        nullptr
    );
}

bool VLCThumbnailer::takeSnapshot(FilePtr file, VLC::MediaPlayer &mp, void *data)
{
    std::unique_ptr<uint8_t[]> buff;
    // lock, signal that we want a snapshot, and wait.
    {
        std::unique_lock<std::mutex> lock( m_mutex );
        m_snapshotRequired = true;
        bool success = m_cond.wait_for( lock, std::chrono::seconds( 3 ), [this]() {
            // Keep waiting if the vmem thread hasn't restored m_snapshotRequired to false
            return m_snapshotRequired == true;
        });
        if ( success == false )
        {
            m_cb->done( file, StatusError, data );
            return false;
        }
        // Prevent the vmem from screwing our snapshot over.
        buff = std::move( m_buff );
        // set a new buffer for next snapshot, and to avoid crashing while we stop the media player
        m_buff.reset( new uint8_t[Width * m_height * Bpp] );
    }
    mp.stop();
    return compress( buff.get(), file, data );
}

#ifdef WITH_JPEG

struct jpegError : public jpeg_error_mgr
{
    jmp_buf buff;
    char message[JMSG_LENGTH_MAX];

    static void jpegErrorHandler(j_common_ptr common)
    {
        auto error = reinterpret_cast<jpegError*>(common->err);
        (*error->format_message)( common, error->message );
        longjmp(error->buff, 1);
    }
};

#endif

bool VLCThumbnailer::compress(uint8_t* buff, FilePtr file, void *data)
{
    auto path = m_ml->snapshotPath();
    path += "/";
    path += std::to_string( file->id() ) + ".jpg";

    //FIXME: Abstract this away, though libjpeg requires a FILE*...
    auto fOut = fopen(path.c_str(), "wb");
    // ensure we always close the file.
    auto fOutPtr = std::unique_ptr<FILE, int(*)(FILE*)>( fOut, &fclose );
    if ( fOut == nullptr )
    {
        LOG_ERROR("Failed to open snapshot file ", path);
        m_cb->done( file, StatusError, data );
        return false;
    }

#ifdef WITH_JPEG
    jpeg_compress_struct compInfo;
    JSAMPROW row_pointer[1];

    //libjpeg's default error handling is to call exit(), which would
    //be slightly problematic...
    jpegError err;
    compInfo.err = jpeg_std_error(&err);
    err.error_exit = jpegError::jpegErrorHandler;

    if ( setjmp( err.buff ) )
    {
        LOG_ERROR("JPEG failure: ", err.message);
        jpeg_destroy_compress(&compInfo);
        m_cb->done( file, StatusError, data );
        return false;
    }

    jpeg_create_compress(&compInfo);
    jpeg_stdio_dest(&compInfo, fOut);

    compInfo.image_width = Width;
    compInfo.image_height = m_height;
    compInfo.input_components = Bpp;
#if JPEG_LIB_VERSION <= 80
    compInfo.in_color_space = JCS_EXT_BGRX;
#else
    compInfo.in_color_space = JCS_RGB;
#endif
    jpeg_set_defaults( &compInfo );
    jpeg_set_quality( &compInfo, 85, TRUE );

    jpeg_start_compress( &compInfo, TRUE );

    auto stride = compInfo.image_width * Bpp;

    while (compInfo.next_scanline < compInfo.image_height) {
      row_pointer[0] = &buff[compInfo.next_scanline * stride];
      jpeg_write_scanlines(&compInfo, row_pointer, 1);
    }
    jpeg_finish_compress(&compInfo);
    jpeg_destroy_compress(&compInfo);
#else
#error FIXME
#endif

    m_cb->done( file, StatusSuccess, data );
    file->setSnapshot( path );
    return true;
}
<commit_msg>Update the file before signaling it as updated<commit_after>#include "VLCThumbnailer.h"

#include <cstring>
#include <jpeglib.h>
#include <setjmp.h>

#include "IFile.h"
#include "logging/Logger.h"

VLCThumbnailer::VLCThumbnailer(const VLC::Instance &vlc)
    : m_instance( vlc )
    , m_snapshotRequired( false )
    , m_height( 0 )
{
}

bool VLCThumbnailer::initialize(IMetadataServiceCb *callback, IMediaLibrary *ml)
{
    m_cb = callback;
    m_ml = ml;
    return true;
}

unsigned int VLCThumbnailer::priority() const
{
    // This needs to be lower than the VLCMetadataService, since we want to know the file type.
    return 50;
}

bool VLCThumbnailer::run( FilePtr file, void *data )
{
    if ( file->type() == IFile::Type::UnknownType )
    {
        // If we don't know the file type yet, it actually looks more like a bug
        // since this should run after file type deduction, and not run in case
        // that step fails.
        m_cb->done( file, StatusError, data );
        return false;
    }
    else if ( file->type() != IFile::Type::VideoType )
    {
        // There's no point in generating a thumbnail for a non-video file.
        m_cb->done( file, StatusSuccess, data );
        return true;
    }

    VLC::Media media( m_instance, file->mrl(), VLC::Media::FromPath );
    media.addOption( ":no-audio" );
    VLC::MediaPlayer mp( media );

    setupVout( mp );

    if ( startPlayback( file, mp, data ) == false )
        return false;

    // Seek ahead to have a significant preview
    if ( seekAhead( file, mp, data ) == false )
        return false;

    if ( takeSnapshot( file, mp, data ) == false )
        return false;
    return true;
}

bool VLCThumbnailer::startPlayback( FilePtr file, VLC::MediaPlayer &mp, void* data )
{
    bool failed = true;

    std::unique_lock<std::mutex> lock( m_mutex );

    mp.eventManager().onPlaying([this, &failed]() {
        std::unique_lock<std::mutex> lock( m_mutex );
        failed = false;
        m_cond.notify_all();
    });
    mp.eventManager().onEncounteredError([this]() {
        std::unique_lock<std::mutex> lock( m_mutex );
        m_cond.notify_all();
    });
    mp.play();
    bool success = m_cond.wait_for( lock, std::chrono::seconds( 3 ), [&mp]() {
        return mp.state() == libvlc_Playing || mp.state() == libvlc_Error;
    });
    if ( success == false || failed == true )
    {
        // In case of timeout or error, don't go any further
        m_cb->done( file, StatusError, data );
        return false;
    }
    return true;
}

bool VLCThumbnailer::seekAhead( FilePtr file, VLC::MediaPlayer& mp, void* data )
{
    std::unique_lock<std::mutex> lock( m_mutex );
    float pos = .0f;
    auto event = mp.eventManager().onPositionChanged([this, &pos](float p) {
        std::unique_lock<std::mutex> lock( m_mutex );
        pos = p;
        m_cond.notify_all();
    });
    mp.setPosition( .4f );
    bool success = m_cond.wait_for( lock, std::chrono::seconds( 3 ), [&pos]() {
        return pos >= .1f;
    });
    // Since we're locking a mutex for each position changed, let's unregister ASAP
    event->unregister();
    if ( success == false )
    {
        m_cb->done( file, StatusError, data );
        return false;
    }
    return true;
}

void VLCThumbnailer::setupVout( VLC::MediaPlayer& mp )
{
    mp.setVideoFormatCallbacks(
        // Setup
        [this, &mp](char* chroma, unsigned int* width, unsigned int *height, unsigned int *pitches, unsigned int *lines) {
            strcpy( chroma, "RV24" );

            const float inputAR = (float)*width / *height;

            *width = Width;
            auto prevHeight = m_height;
            m_height = (float)Width / inputAR + 1;
            // If our buffer isn't enough anymore, reallocate a new one.
            if ( m_height > prevHeight )
                m_buff.reset( new uint8_t[Width * m_height * Bpp] );
            *height = m_height;
            *pitches = Width * Bpp;
            *lines = m_height;
            return 1;
        },
        // Cleanup
        nullptr);
    mp.setVideoCallbacks(
        // Lock
        [this](void** pp_buff) {
            *pp_buff = m_buff.get();
            return nullptr;
        },
        //unlock
        [this](void*, void*const*) {
            std::unique_lock<std::mutex> lock(m_mutex);
            if ( m_snapshotRequired == true )
            {
                m_snapshotRequired = false;
                m_cond.notify_all();
            }
        }
        ,
        //display
        nullptr
    );
}

bool VLCThumbnailer::takeSnapshot(FilePtr file, VLC::MediaPlayer &mp, void *data)
{
    std::unique_ptr<uint8_t[]> buff;
    // lock, signal that we want a snapshot, and wait.
    {
        std::unique_lock<std::mutex> lock( m_mutex );
        m_snapshotRequired = true;
        bool success = m_cond.wait_for( lock, std::chrono::seconds( 3 ), [this]() {
            // Keep waiting if the vmem thread hasn't restored m_snapshotRequired to false
            return m_snapshotRequired == true;
        });
        if ( success == false )
        {
            m_cb->done( file, StatusError, data );
            return false;
        }
        // Prevent the vmem from screwing our snapshot over.
        buff = std::move( m_buff );
        // set a new buffer for next snapshot, and to avoid crashing while we stop the media player
        m_buff.reset( new uint8_t[Width * m_height * Bpp] );
    }
    mp.stop();
    return compress( buff.get(), file, data );
}

#ifdef WITH_JPEG

struct jpegError : public jpeg_error_mgr
{
    jmp_buf buff;
    char message[JMSG_LENGTH_MAX];

    static void jpegErrorHandler(j_common_ptr common)
    {
        auto error = reinterpret_cast<jpegError*>(common->err);
        (*error->format_message)( common, error->message );
        longjmp(error->buff, 1);
    }
};

#endif

bool VLCThumbnailer::compress(uint8_t* buff, FilePtr file, void *data)
{
    auto path = m_ml->snapshotPath();
    path += "/";
    path += std::to_string( file->id() ) + ".jpg";

    //FIXME: Abstract this away, though libjpeg requires a FILE*...
    auto fOut = fopen(path.c_str(), "wb");
    // ensure we always close the file.
    auto fOutPtr = std::unique_ptr<FILE, int(*)(FILE*)>( fOut, &fclose );
    if ( fOut == nullptr )
    {
        LOG_ERROR("Failed to open snapshot file ", path);
        m_cb->done( file, StatusError, data );
        return false;
    }

#ifdef WITH_JPEG
    jpeg_compress_struct compInfo;
    JSAMPROW row_pointer[1];

    //libjpeg's default error handling is to call exit(), which would
    //be slightly problematic...
    jpegError err;
    compInfo.err = jpeg_std_error(&err);
    err.error_exit = jpegError::jpegErrorHandler;

    if ( setjmp( err.buff ) )
    {
        LOG_ERROR("JPEG failure: ", err.message);
        jpeg_destroy_compress(&compInfo);
        m_cb->done( file, StatusError, data );
        return false;
    }

    jpeg_create_compress(&compInfo);
    jpeg_stdio_dest(&compInfo, fOut);

    compInfo.image_width = Width;
    compInfo.image_height = m_height;
    compInfo.input_components = Bpp;
#if JPEG_LIB_VERSION <= 80
    compInfo.in_color_space = JCS_EXT_BGRX;
#else
    compInfo.in_color_space = JCS_RGB;
#endif
    jpeg_set_defaults( &compInfo );
    jpeg_set_quality( &compInfo, 85, TRUE );

    jpeg_start_compress( &compInfo, TRUE );

    auto stride = compInfo.image_width * Bpp;

    while (compInfo.next_scanline < compInfo.image_height) {
      row_pointer[0] = &buff[compInfo.next_scanline * stride];
      jpeg_write_scanlines(&compInfo, row_pointer, 1);
    }
    jpeg_finish_compress(&compInfo);
    jpeg_destroy_compress(&compInfo);
#else
#error FIXME
#endif

    file->setSnapshot( path );
    m_cb->done( file, StatusSuccess, data );
    return true;
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 *
 */

#include <QElapsedTimer>
#include <QTimer>
#include <deconz/dbg_trace.h>
#include "event.h"
#include "resource.h"
#include "device_tick.h"

#define DEV_TICK_BOOT_TIME 8000
#define TICK_INTERVAL_JOIN 500
#define TICK_INTERVAL_IDLE 1000
#define TICK_INTERVAL_IDLE_OTAU 6000

extern int DEV_ApsQueueSize();
extern bool DEV_OtauBusy();

struct JoinDevice
{
    DeviceKey deviceKey;
    quint8 macCapabilities;
};

static const char *RLocal = nullptr;

typedef void (*DT_StateHandler)(DeviceTickPrivate *d, const Event &event);

static void DT_StateInit(DeviceTickPrivate *d, const Event &event);
static void DT_StateJoin(DeviceTickPrivate *d, const Event &event);
static void DT_StateIdle(DeviceTickPrivate *d, const Event &event);

class DeviceTickPrivate
{
public:
    DT_StateHandler stateHandler = DT_StateInit;
    std::vector<JoinDevice> joinDevices;
    DeviceTick *q = nullptr;
    QTimer *timer = nullptr;
    size_t devIter = 0;
    const DeviceContainer *devices = nullptr;
};

/*! Constructor.
 */
DeviceTick::DeviceTick(const DeviceContainer &devices, QObject *parent) :
    QObject(parent),
    d(new DeviceTickPrivate)
{
    d->devices = &devices;
    d->q = this;
    d->timer = new QTimer(this);
    d->timer->setSingleShot(true);
    connect(d->timer, &QTimer::timeout, this, &DeviceTick::timoutFired);
    d->timer->start(DEV_TICK_BOOT_TIME);
}

/*! Destructor.
 */
DeviceTick::~DeviceTick()
{
    Q_ASSERT(d);
    delete d;
    d = nullptr;
}

/*! Public event entry.
 */
void DeviceTick::handleEvent(const Event &event)
{
    d->stateHandler(d, event);
}

/*! State timer callback.
 */
void DeviceTick::timoutFired()
{
    d->stateHandler(d, Event(RLocal, REventStateTimeout, 0));
}

/*! Sets a new DeviceTick state.
    The events REventStateLeave and REventStateEnter will be triggered accordingly.
 */
static void DT_SetState(DeviceTickPrivate *d, DT_StateHandler state)
{
    if (d->stateHandler != state)
    {
        d->stateHandler(d, Event(RLocal, REventStateLeave, 0));
        d->stateHandler = state;
        d->stateHandler(d, Event(RLocal, REventStateEnter, 0));
    }
}

/*! Starts the state timer. Free function to be mocked by test code.
 */
static void DT_StartTimer(DeviceTickPrivate *d, int timeoutMs)
{
    d->timer->start(timeoutMs);
}

/*! Stops the state timer. Free function to be mocked by test code.
 */
static void DT_StopTimer(DeviceTickPrivate *d)
{
    d->timer->stop();
}

/*! Initial state to wait DEV_TICK_BOOT_TIME seconds before starting normal operation.
 */
static void DT_StateInit(DeviceTickPrivate *d, const Event &event)
{
    if (event.resource() == RLocal && event.what() == REventStateTimeout)
    {
        DBG_Printf(DBG_INFO, "DEV Tick.Init: booted after %lld seconds\n", DEV_TICK_BOOT_TIME);
        DT_SetState(d, DT_StateIdle);
    }
}

/*! Emits REventPoll to the next device in DT_StateIdle.
 */
static void DT_PollNextIdleDevice(DeviceTickPrivate *d)
{
    const auto devCount = d->devices->size();

    if (devCount == 0)
    {
        return;
    }

    d->devIter %= devCount;

    const auto &device = d->devices->at(d->devIter);
    Q_ASSERT(device);
    if (device->reachable())
    {
        emit d->q->eventNotify(Event(device->prefix(), REventPoll, 0, device->key()));
    }
    d->devIter++;
}

/*! This state is active while Permit Join is disabled for normal idle operation.

    It walks over all Devices in TICK_INTERVAL_IDLE spacing.
    The state transitions to DT_StateJoin when REventPermitjoinEnabled is received.
 */
static void DT_StateIdle(DeviceTickPrivate *d, const Event &event)
{
    if (event.what() == REventPermitjoinEnabled)
    {
        DT_SetState(d, DT_StateJoin);
    }
    else if (event.resource() == RLocal)
    {
        if (event.what() == REventStateTimeout)
        {
            int timeout = DEV_OtauBusy() ? TICK_INTERVAL_IDLE_OTAU : TICK_INTERVAL_IDLE;
            if (DEV_ApsQueueSize() < 4)
            {
                DT_PollNextIdleDevice(d);
            }
            DT_StartTimer(d, timeout);
        }
        else if (event.what() == REventStateEnter)
        {
            DT_StartTimer(d, TICK_INTERVAL_IDLE);
        }
        else if (event.what() == REventStateLeave)
        {
            DT_StopTimer(d);
        }
    }
}

/*! Adds a joining device entry to the queue if not already present.
 */
static void DT_RegisterJoiningDevice(DeviceTickPrivate *d, DeviceKey deviceKey, quint8 macCapabilities)
{
    Q_ASSERT(deviceKey != 0); // if this triggers we have problems elsewhere

    auto i = std::find_if(d->joinDevices.cbegin(), d->joinDevices.cend(), [&deviceKey](const JoinDevice &dev)
    {
        return deviceKey == dev.deviceKey;
    });

    if (i == d->joinDevices.cend())
    {
        JoinDevice dev;
        dev.deviceKey = deviceKey;
        dev.macCapabilities = macCapabilities;
        d->joinDevices.push_back(dev);
        DBG_Printf(DBG_INFO, "DEV Tick: fast poll 0x%016llX, mac capabilities: 0x%02X\n", deviceKey, macCapabilities);
    }
}

/*! Emits REventPoll to the next device in DT_StateJoin.
 */
static void DT_PollNextJoiningDevice(DeviceTickPrivate *d)
{
    if (d->joinDevices.empty())
    {
        return;
    }

    d->devIter %= d->joinDevices.size();
    Q_ASSERT(d->devIter < d->joinDevices.size());

    const JoinDevice &device = d->joinDevices.at(d->devIter);
    emit d->q->eventNotify(Event(RDevices, REventAwake, 0, device.deviceKey));
    d->devIter++;
}

/*! This state is active while Permit Join is enabled.

    When a REventDeviceAnnounce event is received, the device is added to a joining
    queue and processed exclusivly and quickly.

    The state transitions to DT_StateIdle when Permit Join is disabled.
 */
static void DT_StateJoin(DeviceTickPrivate *d, const Event &event)
{
    if (event.what() == REventPermitjoinDisabled)
    {
        DT_SetState(d, DT_StateIdle);
    }
    else if (event.what() == REventDeviceAnnounce)
    {
        DBG_Printf(DBG_INFO, "DEV Tick.Join: %s\n", event.what());
        DT_RegisterJoiningDevice(d, event.deviceKey(), static_cast<quint8>(event.num()));
    }
    else if (event.resource() == RLocal)
    {
        if (event.what() == REventStateTimeout)
        {
            DT_PollNextJoiningDevice(d);
            DT_StartTimer(d, TICK_INTERVAL_JOIN);
        }
        else if (event.what() == REventStateEnter)
        {
            DT_StartTimer(d, TICK_INTERVAL_JOIN);
        }
        else if (event.what() == REventStateLeave)
        {
            DT_StopTimer(d);
            d->joinDevices.clear();
        }
    }
}
<commit_msg>Let device fast polling continue a few secs after permit join disabled<commit_after>/*
 * Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 *
 */

#include <QElapsedTimer>
#include <QTimer>
#include <deconz/dbg_trace.h>
#include <deconz/timeref.h>
#include "event.h"
#include "resource.h"
#include "device_tick.h"

#define DEV_TICK_BOOT_TIME 8000
#define TICK_INTERVAL_JOIN 500
#define TICK_INTERVAL_IDLE 1000
#define TICK_INTERVAL_IDLE_OTAU 6000

extern int DEV_ApsQueueSize();
extern bool DEV_OtauBusy();

struct JoinDevice
{
    DeviceKey deviceKey;
    quint8 macCapabilities;
};

static const char *RLocal = nullptr;

typedef void (*DT_StateHandler)(DeviceTickPrivate *d, const Event &event);

static void DT_StateInit(DeviceTickPrivate *d, const Event &event);
static void DT_StateJoin(DeviceTickPrivate *d, const Event &event);
static void DT_StateIdle(DeviceTickPrivate *d, const Event &event);

class DeviceTickPrivate
{
public:
    DT_StateHandler stateHandler = DT_StateInit;
    std::vector<JoinDevice> joinDevices;
    deCONZ::SteadyTimeRef joinDisabledTime;
    DeviceTick *q = nullptr;
    QTimer *timer = nullptr;
    size_t devIter = 0;
    const DeviceContainer *devices = nullptr;
};

/*! Constructor.
 */
DeviceTick::DeviceTick(const DeviceContainer &devices, QObject *parent) :
    QObject(parent),
    d(new DeviceTickPrivate)
{
    d->devices = &devices;
    d->q = this;
    d->timer = new QTimer(this);
    d->timer->setSingleShot(true);
    connect(d->timer, &QTimer::timeout, this, &DeviceTick::timoutFired);
    d->timer->start(DEV_TICK_BOOT_TIME);
}

/*! Destructor.
 */
DeviceTick::~DeviceTick()
{
    Q_ASSERT(d);
    delete d;
    d = nullptr;
}

/*! Public event entry.
 */
void DeviceTick::handleEvent(const Event &event)
{
    d->stateHandler(d, event);
}

/*! State timer callback.
 */
void DeviceTick::timoutFired()
{
    d->stateHandler(d, Event(RLocal, REventStateTimeout, 0));
}

/*! Sets a new DeviceTick state.
    The events REventStateLeave and REventStateEnter will be triggered accordingly.
 */
static void DT_SetState(DeviceTickPrivate *d, DT_StateHandler state)
{
    if (d->stateHandler != state)
    {
        d->stateHandler(d, Event(RLocal, REventStateLeave, 0));
        d->stateHandler = state;
        d->stateHandler(d, Event(RLocal, REventStateEnter, 0));
    }
}

/*! Starts the state timer. Free function to be mocked by test code.
 */
static void DT_StartTimer(DeviceTickPrivate *d, int timeoutMs)
{
    d->timer->start(timeoutMs);
}

/*! Stops the state timer. Free function to be mocked by test code.
 */
static void DT_StopTimer(DeviceTickPrivate *d)
{
    d->timer->stop();
}

/*! Initial state to wait DEV_TICK_BOOT_TIME seconds before starting normal operation.
 */
static void DT_StateInit(DeviceTickPrivate *d, const Event &event)
{
    if (event.resource() == RLocal && event.what() == REventStateTimeout)
    {
        DBG_Printf(DBG_INFO, "DEV Tick.Init: booted after %lld seconds\n", DEV_TICK_BOOT_TIME);
        DT_SetState(d, DT_StateIdle);
    }
}

/*! Emits REventPoll to the next device in DT_StateIdle.
 */
static void DT_PollNextIdleDevice(DeviceTickPrivate *d)
{
    const auto devCount = d->devices->size();

    if (devCount == 0)
    {
        return;
    }

    d->devIter %= devCount;

    const auto &device = d->devices->at(d->devIter);
    Q_ASSERT(device);
    if (device->reachable())
    {
        emit d->q->eventNotify(Event(device->prefix(), REventPoll, 0, device->key()));
    }
    d->devIter++;
}

/*! This state is active while Permit Join is disabled for normal idle operation.

    It walks over all Devices in TICK_INTERVAL_IDLE spacing.
    The state transitions to DT_StateJoin when REventPermitjoinEnabled is received.
 */
static void DT_StateIdle(DeviceTickPrivate *d, const Event &event)
{
    if (event.what() == REventPermitjoinEnabled)
    {
        DT_SetState(d, DT_StateJoin);
    }
    else if (event.resource() == RLocal)
    {
        if (event.what() == REventStateTimeout)
        {
            int timeout = DEV_OtauBusy() ? TICK_INTERVAL_IDLE_OTAU : TICK_INTERVAL_IDLE;
            if (DEV_ApsQueueSize() < 4)
            {
                DT_PollNextIdleDevice(d);
            }
            DT_StartTimer(d, timeout);
        }
        else if (event.what() == REventStateEnter)
        {
            DT_StartTimer(d, TICK_INTERVAL_IDLE);
        }
        else if (event.what() == REventStateLeave)
        {
            DT_StopTimer(d);
        }
    }
}

/*! Adds a joining device entry to the queue if not already present.
 */
static void DT_RegisterJoiningDevice(DeviceTickPrivate *d, DeviceKey deviceKey, quint8 macCapabilities)
{
    Q_ASSERT(deviceKey != 0); // if this triggers we have problems elsewhere

    auto i = std::find_if(d->joinDevices.cbegin(), d->joinDevices.cend(), [&deviceKey](const JoinDevice &dev)
    {
        return deviceKey == dev.deviceKey;
    });

    if (i == d->joinDevices.cend())
    {
        JoinDevice dev;
        dev.deviceKey = deviceKey;
        dev.macCapabilities = macCapabilities;
        d->joinDevices.push_back(dev);
        DBG_Printf(DBG_INFO, "DEV Tick: fast poll 0x%016llX, mac capabilities: 0x%02X\n", deviceKey, macCapabilities);
    }
}

/*! Emits REventPoll to the next device in DT_StateJoin.
 */
static void DT_PollNextJoiningDevice(DeviceTickPrivate *d)
{
    if (d->joinDevices.empty())
    {
        return;
    }

    d->devIter %= d->joinDevices.size();
    Q_ASSERT(d->devIter < d->joinDevices.size());

    const JoinDevice &device = d->joinDevices.at(d->devIter);
    emit d->q->eventNotify(Event(RDevices, REventAwake, 0, device.deviceKey));
    d->devIter++;
}

/*! This state is active while Permit Join is enabled.

    When a REventDeviceAnnounce event is received, the device is added to a joining
    queue and processed exclusivly and quickly.

    The state transitions to DT_StateIdle when Permit Join is disabled.
 */
static void DT_StateJoin(DeviceTickPrivate *d, const Event &event)
{
    if (event.what() == REventPermitjoinDisabled)
    {
        d->joinDisabledTime = deCONZ::steadyTimeRef();
    }
    else if (event.what() == REventDeviceAnnounce)
    {
        DBG_Printf(DBG_INFO, "DEV Tick.Join: %s\n", event.what());
        DT_RegisterJoiningDevice(d, event.deviceKey(), static_cast<quint8>(event.num()));
    }
    else if (event.resource() == RLocal)
    {
        if (event.what() == REventStateTimeout)
        {
            if (deCONZ::isValid(d->joinDisabledTime) &&
                deCONZ::TimeSeconds{20} < (deCONZ::steadyTimeRef() - d->joinDisabledTime))
            {
                // leave state after delay to let fast polling finish even then permit join is disabled again
                DT_SetState(d, DT_StateIdle);
                return;
            }

            DT_PollNextJoiningDevice(d);
            DT_StartTimer(d, TICK_INTERVAL_JOIN);
        }
        else if (event.what() == REventStateEnter)
        {
            d->joinDisabledTime = {};
            DT_StartTimer(d, TICK_INTERVAL_JOIN);
        }
        else if (event.what() == REventStateLeave)
        {
            DT_StopTimer(d);
            d->joinDevices.clear();
        }
    }
}
<|endoftext|>


<commit_before>//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice,
//      this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the author nor the names of other contributors may
//      be used to endorse or promote products derived from this software
//      without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////

#include <stdafx.h>

#include "TrackRowRenderers.h"
#include <cursespp/Colors.h>
#include <cursespp/SingleLineEntry.h>
#include <cursespp/Text.h>
#include <core/support/PreferenceKeys.h>
#include <core/library/LocalLibraryConstants.h>
#include <core/support/Duration.h>
#include <app/util/Rating.h>
#include <app/util/PreferenceKeys.h>
#include <math.h>

using namespace musik::core;
using namespace musik::core::library;
using namespace musik::cube;
using namespace musik::cube::TrackRowRenderers;
using namespace cursespp;

#define DIGITS(x) (x > 9 ? (int) log10((double) x) + 1 : 1)

static const int kDurationColWidth = 5; /* 00:00 */
static const int kRatingBreakpointWidth = 90;

static auto preferences = Preferences::ForComponent(musik::core::prefs::components::Settings);

static inline std::string getRatingForTrack(TrackPtr track, size_t width) {
    if (preferences->GetBool(musik::cube::prefs::keys::DisableRatingColumn, false)) {
        return "";
    }
    int rating = std::max(0, std::min(5, track->GetInt32("rating", 0)));
    return (width > kRatingBreakpointWidth) ? "   " + getRatingString(rating) : "";
}

namespace AlbumSort {
    static const int kTrackColWidth = 3;
    static const int kArtistColWidth = 17;
    static const int kRatingColumnWidth = 5;

    static Renderer renderer = [](TrackPtr track, size_t index, size_t width, TrackNumType type) -> std::string {
        std::string trackNum;

        std::string rating = getRatingForTrack(track, width);

        int trackColWidth = kTrackColWidth;
        if (type == TrackNumType::Metadata) {
            trackNum = text::Align(
                track->GetString(constants::Track::TRACK_NUM),
                text::AlignRight,
                kTrackColWidth);
        }
        else {
            trackColWidth = std::max(kTrackColWidth, DIGITS(index + 1));
            trackNum = text::Align(std::to_string(index + 1), text::AlignRight, trackColWidth);
        }

        std::string duration = text::Align(
            musik::core::duration::Duration(track->GetString(constants::Track::DURATION)),
            text::AlignRight,
            kDurationColWidth);

        std::string artist = text::Align(
            track->GetString(constants::Track::ARTIST),
            text::AlignLeft,
            kArtistColWidth);

        int titleWidth = 0;

        titleWidth =
            (int) width -
            (int) trackColWidth -
            kDurationColWidth -
            kArtistColWidth -
            u8len(rating) -
            (3 * 3); /* 3 = spacing */

        titleWidth = std::max(0, titleWidth);

        std::string title = text::Align(
            track->GetString(constants::Track::TITLE),
            text::AlignLeft,
            (int) titleWidth);

        return u8fmt(
            "%s   %s%s   %s   %s",
            trackNum.c_str(),
            title.c_str(),
            rating.c_str(),
            duration.c_str(),
            artist.c_str());
    };
}

namespace NowPlaying {
    static const int kTrackColWidth = 3;
    static const int kArtistColWidth = 14;
    static const int kAlbumColWidth = 14;

    static Renderer renderer = [](TrackPtr track, size_t index, size_t width, TrackNumType type) -> std::string {
        size_t trackColWidth = std::max(kTrackColWidth, DIGITS(index + 1));
        std::string trackNum = text::Align(std::to_string(index + 1), text::AlignRight, trackColWidth);
        std::string rating = getRatingForTrack(track, width);

        std::string duration = text::Align(
            duration::Duration(track->GetString(constants::Track::DURATION)),
            text::AlignRight,
            kDurationColWidth);

        std::string album = text::Align(
            track->GetString(constants::Track::ALBUM),
            text::AlignLeft,
            kAlbumColWidth);

        std::string artist = text::Align(
            track->GetString(constants::Track::ARTIST),
            text::AlignLeft,
            kArtistColWidth);

        int titleWidth =
            (int) width -
            (int) trackColWidth -
            kDurationColWidth -
            kAlbumColWidth -
            kArtistColWidth -
            u8cols(rating) -
            (4 * 3); /* 3 = spacing */

        titleWidth = std::max(0, titleWidth);

        std::string title = text::Align(
            track->GetString(constants::Track::TITLE),
            text::AlignLeft,
            (int) titleWidth);

        return u8fmt(
            "%s   %s%s   %s   %s   %s",
            trackNum.c_str(),
            title.c_str(),
            rating.c_str(),
            duration.c_str(),
            album.c_str(),
            artist.c_str());
    };
}

namespace musik {
    namespace cube {
        namespace TrackRowRenderers {
            const Renderer Get(Type type) {
                switch (type) {
                    case Type::AlbumSort: return AlbumSort::renderer;
                    case Type::NowPlaying: return NowPlaying::renderer;
                }
                throw std::runtime_error("invalid type specified to TrackRowRenderers::Get");
            }
        }
    }
}
<commit_msg>Minor rating column cleanups.<commit_after>//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice,
//      this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the author nor the names of other contributors may
//      be used to endorse or promote products derived from this software
//      without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////

#include <stdafx.h>

#include "TrackRowRenderers.h"
#include <cursespp/Colors.h>
#include <cursespp/SingleLineEntry.h>
#include <cursespp/Text.h>
#include <core/support/PreferenceKeys.h>
#include <core/library/LocalLibraryConstants.h>
#include <core/support/Duration.h>
#include <app/util/Rating.h>
#include <app/util/PreferenceKeys.h>
#include <math.h>

using namespace musik::core;
using namespace musik::core::library;
using namespace musik::cube;
using namespace musik::cube::TrackRowRenderers;
using namespace cursespp;

#define DIGITS(x) (x > 9 ? (int) log10((double) x) + 1 : 1)

static const int kDurationColWidth = 5; /* 00:00 */
static const int kRatingBreakpointWidth = 90;

/* this method does a couple things slower than it probably should, but it
shouldn't cause any issues. TODO: make this better? does it matter? */
static inline std::string getRatingForTrack(TrackPtr track, size_t width) {
    if (width <= kRatingBreakpointWidth) {
        return "";
    }
    auto p = Preferences::ForComponent(musik::core::prefs::components::Settings);
    if (p->GetBool(musik::cube::prefs::keys::DisableRatingColumn, false)) {
        return "";
    }
    int rating = std::max(0, std::min(5, track->GetInt32("rating", 0)));
    return "   " + getRatingString(rating);
}

namespace AlbumSort {
    static const int kTrackColWidth = 3;
    static const int kArtistColWidth = 17;
    static const int kRatingColumnWidth = 5;

    static Renderer renderer = [](TrackPtr track, size_t index, size_t width, TrackNumType type) -> std::string {
        std::string trackNum;

        std::string rating = getRatingForTrack(track, width);

        int trackColWidth = kTrackColWidth;
        if (type == TrackNumType::Metadata) {
            trackNum = text::Align(
                track->GetString(constants::Track::TRACK_NUM),
                text::AlignRight,
                kTrackColWidth);
        }
        else {
            trackColWidth = std::max(kTrackColWidth, DIGITS(index + 1));
            trackNum = text::Align(std::to_string(index + 1), text::AlignRight, trackColWidth);
        }

        std::string duration = text::Align(
            musik::core::duration::Duration(track->GetString(constants::Track::DURATION)),
            text::AlignRight,
            kDurationColWidth);

        std::string artist = text::Align(
            track->GetString(constants::Track::ARTIST),
            text::AlignLeft,
            kArtistColWidth);

        int titleWidth = 0;

        titleWidth =
            (int) width -
            (int) trackColWidth -
            kDurationColWidth -
            kArtistColWidth -
            (int) u8len(rating) -
            (3 * 3); /* 3 = spacing */

        titleWidth = std::max(0, titleWidth);

        std::string title = text::Align(
            track->GetString(constants::Track::TITLE),
            text::AlignLeft,
            (int) titleWidth);

        return u8fmt(
            "%s   %s%s   %s   %s",
            trackNum.c_str(),
            title.c_str(),
            rating.c_str(),
            duration.c_str(),
            artist.c_str());
    };
}

namespace NowPlaying {
    static const int kTrackColWidth = 3;
    static const int kArtistColWidth = 14;
    static const int kAlbumColWidth = 14;

    static Renderer renderer = [](TrackPtr track, size_t index, size_t width, TrackNumType type) -> std::string {
        size_t trackColWidth = std::max(kTrackColWidth, DIGITS(index + 1));
        std::string trackNum = text::Align(std::to_string(index + 1), text::AlignRight, trackColWidth);
        std::string rating = getRatingForTrack(track, width);

        std::string duration = text::Align(
            duration::Duration(track->GetString(constants::Track::DURATION)),
            text::AlignRight,
            kDurationColWidth);

        std::string album = text::Align(
            track->GetString(constants::Track::ALBUM),
            text::AlignLeft,
            kAlbumColWidth);

        std::string artist = text::Align(
            track->GetString(constants::Track::ARTIST),
            text::AlignLeft,
            kArtistColWidth);

        int titleWidth =
            (int) width -
            (int) trackColWidth -
            kDurationColWidth -
            kAlbumColWidth -
            kArtistColWidth -
            (int) u8cols(rating) -
            (4 * 3); /* 3 = spacing */

        titleWidth = std::max(0, titleWidth);

        std::string title = text::Align(
            track->GetString(constants::Track::TITLE),
            text::AlignLeft,
            (int) titleWidth);

        return u8fmt(
            "%s   %s%s   %s   %s   %s",
            trackNum.c_str(),
            title.c_str(),
            rating.c_str(),
            duration.c_str(),
            album.c_str(),
            artist.c_str());
    };
}

namespace musik {
    namespace cube {
        namespace TrackRowRenderers {
            const Renderer Get(Type type) {
                switch (type) {
                    case Type::AlbumSort: return AlbumSort::renderer;
                    case Type::NowPlaying: return NowPlaying::renderer;
                }
                throw std::runtime_error("invalid type specified to TrackRowRenderers::Get");
            }
        }
    }
}
<|endoftext|>


<commit_before>/*
The MIT License

Copyright (c) 2011 by Jorrit Tyberghein

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */

#include <crystalspace.h>

#include "meshfactmodel.h"
#include "edcommon/tools.h"
#include "propclass/dynworld.h"

using namespace Ares;

void MeshCollectionValue::UpdateChildren ()
{
  if (!dirty) return;
  dirty = false;
  ReleaseChildren ();
  iMeshFactoryList* list = engine->GetMeshFactories ();
  for (size_t i = 0 ; i < size_t (list->GetCount ()) ; i++)
  {
    iMeshFactoryWrapper* fact = list->Get (i);
    const char* name = fact->QueryObject ()->GetName ();
    if (!dynworld->FindFactory (name))
      NewCompositeChild (VALUE_STRING, "name", name, VALUE_NONE);
  }
}

<commit_msg>The list of factories in the dynamic factory editor is now sorted for easier finding of new factories.<commit_after>/*
The MIT License

Copyright (c) 2011 by Jorrit Tyberghein

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */

#include <crystalspace.h>

#include "meshfactmodel.h"
#include "edcommon/tools.h"
#include "propclass/dynworld.h"

using namespace Ares;

void MeshCollectionValue::UpdateChildren ()
{
  if (!dirty) return;
  dirty = false;
  ReleaseChildren ();
  iMeshFactoryList* list = engine->GetMeshFactories ();
  csStringArray names;
  for (size_t i = 0 ; i < size_t (list->GetCount ()) ; i++)
  {
    iMeshFactoryWrapper* fact = list->Get (i);
    const char* name = fact->QueryObject ()->GetName ();
    if (!dynworld->FindFactory (name))
      names.Push (name);
  }
  names.Sort ();

  for (size_t i = 0 ; i < names.GetSize () ; i++)
  {
    NewCompositeChild (VALUE_STRING, "name", names[i], VALUE_NONE);
  }
}

<|endoftext|>


<commit_before>/*
Copyright (c) 2012 Carsten Burstedde, Donna Calhoun
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "filament_user.h"

#include <fclaw2d_forestclaw.h>
#include <fclaw2d_clawpatch.h>

#if 0
static fclaw2d_vtable_t fclaw2d_vt;

static fc2d_clawpack46_vtable_t classic_claw46;
static fc2d_clawpack5_vtable_t classic_claw5;
#endif

void filament_link_solvers(fclaw2d_domain_t *domain)
{
    const user_options_t* user = filament_user_get_options(domain);
    const amr_options_t* gparms = fclaw2d_forestclaw_get_options(domain);

    // fclaw2d_init_vtable();

    if (user->claw_version == 4)
    {
        // fc2d_clawpack46_set_vtable_defaults(&fclaw2d_vt, &classic_claw46);
        // fc2d_clawpack46_set_vtable_defaults();

        fc2d_clawpack46_vt()->setprob   = &SETPROB;
        fc2d_clawpack46_vt()->qinit     = &CLAWPACK46_QINIT;
        if (gparms->manifold)
        {
            fclaw2d_patch_vt()->patch_setup   = &filament_patch_setup_manifold;

            fc2d_clawpack46_vt()->rpn2      = &CLAWPACK46_RPN2ADV_MANIFOLD;
            fc2d_clawpack46_vt()->rpt2      = &CLAWPACK46_RPT2ADV_MANIFOLD;

            if (user->example == 2)
            {
                /* Avoid tagging block corners in 5 patch example*/
                fclaw2d_clawpatch_vt()->fort_tag4refinement = &CLAWPACK46_TAG4REFINEMENT;
                fclaw2d_clawpatch_vt()->fort_tag4coarsening = &CLAWPACK46_TAG4COARSENING;
            }
        }
        else
        {
            fc2d_clawpack46_vt()->setaux    = &CLAWPACK46_SETAUX;  /* Used in non-manifold case */
            fc2d_clawpack46_vt()->rpn2      = &CLAWPACK46_RPN2ADV;
            fc2d_clawpack46_vt()->rpt2      = &CLAWPACK46_RPT2ADV;
        }

        // fc2d_clawpack46_set_vtable(classic_claw46);
    }
    else if (user->claw_version == 5)
    {
        // fc2d_clawpack5_set_vtable_defaults(&fclaw2d_vt, &classic_claw5);
        // fc2d_clawpack5_set_vtable_defaults();

        fc2d_clawpack5_vt()->setprob   = &SETPROB;
        fc2d_clawpack5_vt()->qinit     = &CLAWPACK5_QINIT;
        if (gparms->manifold)
        {
            fclaw2d_patch_vt()->patch_setup  = &filament_patch_setup_manifold;
            fc2d_clawpack5_vt()->rpn2      = &CLAWPACK5_RPN2ADV_MANIFOLD;
            fc2d_clawpack5_vt()->rpt2      = &CLAWPACK5_RPT2ADV_MANIFOLD;

            if (user->example == 2)
            {
                /* Avoid tagging block corners in 5 patch example*/
                fclaw2d_clawpatch_vt()->fort_tag4refinement = &CLAWPACK5_TAG4REFINEMENT;
                fclaw2d_clawpatch_vt()->fort_tag4coarsening = &CLAWPACK5_TAG4COARSENING;
            }
        }
        else
        {
            fc2d_clawpack5_vt()->setaux    = &CLAWPACK5_SETAUX;   /* Used in non-manifold case */
            fc2d_clawpack5_vt()->rpn2      = &CLAWPACK5_RPN2ADV;
            fc2d_clawpack5_vt()->rpt2      = &CLAWPACK5_RPT2ADV;
        }

        // fc2d_clawpack5_set_vtable(classic_claw5);
    }
    fclaw2d_set_vtable();
}

void filament_patch_setup_manifold(fclaw2d_domain_t *domain,
                                   fclaw2d_patch_t *this_patch,
                                   int this_block_idx,
                                   int this_patch_idx)
{
    const user_options_t* user = filament_user_get_options(domain);

    int mx,my,mbc,maux;
    double xlower,ylower,dx,dy;
    double *aux,*xd,*yd,*zd,*area;
    double *xp,*yp,*zp;

    fclaw2d_clawpatch_grid_data(domain,this_patch,&mx,&my,&mbc,
                                &xlower,&ylower,&dx,&dy);

    fclaw2d_clawpatch_metric_data(domain,this_patch,&xp,&yp,&zp,
                                  &xd,&yd,&zd,&area);

    if (user->claw_version == 4)
    {
        fc2d_clawpack46_define_auxarray(domain,this_patch);
        fc2d_clawpack46_aux_data(domain,this_patch,&aux,&maux);
        USER46_SETAUX_MANIFOLD(&mbc,&mx,&my,&xlower,&ylower,
                               &dx,&dy,&maux,aux,&this_block_idx,
                               xd,yd,zd,area);
    }
    else if (user->claw_version == 5)
    {
        fc2d_clawpack5_define_auxarray(domain,this_patch);
        fc2d_clawpack5_aux_data(domain,this_patch,&aux,&maux);
        USER5_SETAUX_MANIFOLD(&mbc,&mx,&my,&xlower,&ylower,
                              &dx,&dy,&maux,aux,&this_block_idx,
                              xd,yd,zd,area);
    }
}
<commit_msg>(filament) No longer necessary to call init routines in vtable setup<commit_after>/*
Copyright (c) 2012 Carsten Burstedde, Donna Calhoun
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "filament_user.h"

#include <fclaw2d_forestclaw.h>
#include <fclaw2d_clawpatch.h>

void filament_link_solvers(fclaw2d_domain_t *domain)
{
    const user_options_t* user = filament_user_get_options(domain);
    const amr_options_t* gparms = fclaw2d_forestclaw_get_options(domain);

    // fclaw2d_init_vtable();

    if (user->claw_version == 4)
    {
        fc2d_clawpack46_vt()->setprob   = &SETPROB;
        fc2d_clawpack46_vt()->qinit     = &CLAWPACK46_QINIT;
        if (gparms->manifold)
        {
            fclaw2d_patch_vt()->patch_setup   = &filament_patch_setup_manifold;

            fc2d_clawpack46_vt()->rpn2      = &CLAWPACK46_RPN2ADV_MANIFOLD;
            fc2d_clawpack46_vt()->rpt2      = &CLAWPACK46_RPT2ADV_MANIFOLD;

            if (user->example == 2)
            {
                /* Avoid tagging block corners in 5 patch example*/
                fclaw2d_clawpatch_vt()->fort_tag4refinement = &CLAWPACK46_TAG4REFINEMENT;
                fclaw2d_clawpatch_vt()->fort_tag4coarsening = &CLAWPACK46_TAG4COARSENING;
            }
        }
        else
        {
            fc2d_clawpack46_vt()->setaux    = &CLAWPACK46_SETAUX;  /* Used in non-manifold case */
            fc2d_clawpack46_vt()->rpn2      = &CLAWPACK46_RPN2ADV;
            fc2d_clawpack46_vt()->rpt2      = &CLAWPACK46_RPT2ADV;
        }

    }
    else if (user->claw_version == 5)
    {
        fc2d_clawpack5_vt()->setprob   = &SETPROB;
        fc2d_clawpack5_vt()->qinit     = &CLAWPACK5_QINIT;
        if (gparms->manifold)
        {
            fclaw2d_patch_vt()->patch_setup  = &filament_patch_setup_manifold;
            fc2d_clawpack5_vt()->rpn2      = &CLAWPACK5_RPN2ADV_MANIFOLD;
            fc2d_clawpack5_vt()->rpt2      = &CLAWPACK5_RPT2ADV_MANIFOLD;

            if (user->example == 2)
            {
                /* Avoid tagging block corners in 5 patch example*/
                fclaw2d_clawpatch_vt()->fort_tag4refinement = &CLAWPACK5_TAG4REFINEMENT;
                fclaw2d_clawpatch_vt()->fort_tag4coarsening = &CLAWPACK5_TAG4COARSENING;
            }
        }
        else
        {
            fc2d_clawpack5_vt()->setaux    = &CLAWPACK5_SETAUX;   /* Used in non-manifold case */
            fc2d_clawpack5_vt()->rpn2      = &CLAWPACK5_RPN2ADV;
            fc2d_clawpack5_vt()->rpt2      = &CLAWPACK5_RPT2ADV;
        }
    }
}

void filament_patch_setup_manifold(fclaw2d_domain_t *domain,
                                   fclaw2d_patch_t *this_patch,
                                   int this_block_idx,
                                   int this_patch_idx)
{
    const user_options_t* user = filament_user_get_options(domain);

    int mx,my,mbc,maux;
    double xlower,ylower,dx,dy;
    double *aux,*xd,*yd,*zd,*area;
    double *xp,*yp,*zp;

    fclaw2d_clawpatch_grid_data(domain,this_patch,&mx,&my,&mbc,
                                &xlower,&ylower,&dx,&dy);

    fclaw2d_clawpatch_metric_data(domain,this_patch,&xp,&yp,&zp,
                                  &xd,&yd,&zd,&area);

    if (user->claw_version == 4)
    {
        fc2d_clawpack46_define_auxarray(domain,this_patch);
        fc2d_clawpack46_aux_data(domain,this_patch,&aux,&maux);
        USER46_SETAUX_MANIFOLD(&mbc,&mx,&my,&xlower,&ylower,
                               &dx,&dy,&maux,aux,&this_block_idx,
                               xd,yd,zd,area);
    }
    else if (user->claw_version == 5)
    {
        fc2d_clawpack5_define_auxarray(domain,this_patch);
        fc2d_clawpack5_aux_data(domain,this_patch,&aux,&maux);
        USER5_SETAUX_MANIFOLD(&mbc,&mx,&my,&xlower,&ylower,
                              &dx,&dy,&maux,aux,&this_block_idx,
                              xd,yd,zd,area);
    }
}
<|endoftext|>


<commit_before>/******************************************************************************
*       SOFA, Simulation Open-Framework Architecture, version 1.0 RC 1        *
*                (c) 2006-2011 INRIA, USTL, UJF, CNRS, MGH                    *
*                                                                             *
* This program is free software; you can redistribute it and/or modify it     *
* under the terms of the GNU General Public License as published by the Free  *
* Software Foundation; either version 2 of the License, or (at your option)   *
* any later version.                                                          *
*                                                                             *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    *
* more details.                                                               *
*                                                                             *
* You should have received a copy of the GNU General Public License along     *
* with this program; if not, write to the Free Software Foundation, Inc., 51  *
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.                   *
*******************************************************************************
*                            SOFA :: Applications                             *
*                                                                             *
* Authors: The SOFA Team and external contributors (see Authors.txt)          *
*                                                                             *
* Contact information: contact@sofa-framework.org                             *
******************************************************************************/

#include <plugins/SceneCreator/SceneCreator.h>

#include <sofa/helper/ArgumentParser.h>
#include <sofa/core/ExecParams.h>
#include <sofa/simulation/tree/TreeSimulation.h>
#include <sofa/simulation/common/Node.h>
#include <sofa/simulation/common/DeleteVisitor.h>

#include <sofa/gui/GUIManager.h>
#include <sofa/gui/Main.h>
#include <sofa/helper/system/FileRepository.h>

#include <sofa/component/collision/MinProximityIntersection.h>
#include <sofa/component/constraintset/LMConstraintSolver.h>
#include <sofa/component/odesolver/EulerImplicitSolver.h>
#include <sofa/component/linearsolver/CGLinearSolver.h>
#include <sofa/core/CollisionModel.h>

//Using double by default, if you have SOFA_FLOAT in use in you sofa-default.cfg, then it will be FLOAT.
#include <sofa/component/typedef/Sofa_typedef.h>
// ---------------------------------------------------------------------
// ---
// ---------------------------------------------------------------------
using namespace sofa::simulation;
using namespace sofa::component::container;
using namespace sofa::component::topology;
typedef sofa::component::linearsolver::CGLinearSolver< sofa::component::linearsolver::GraphScatteredMatrix, sofa::component::linearsolver::GraphScatteredVector> CGLinearSolverGraph;

const std::string colors[7]= {"red","green","blue","cyan","magenta","yellow","white"};

SReal convertDegreeToRadian(const SReal& angle)
{
    const SReal pi=3.14159265;
    return angle*pi/180.0;
}

Node::SPtr createCard(Node::SPtr  parent, const Coord3& position, const Coord3& rotation)
{
    const std::string visualModel="mesh/card.obj";
    const std::string collisionModel="mesh/cardCollision.obj";
    const std::string inertiaMatrix="BehaviorModels/card.rigid";
    static int colorIdx=0;

    std::vector<std::string> modelTypes;
    modelTypes.push_back("Triangle");
    modelTypes.push_back("Line");
    modelTypes.push_back("Point");

    Node::SPtr  card = sofa::modeling::CreateEulerSolverNode(parent,"Rigid","Implicit");

    sofa::component::odesolver::EulerImplicitSolver *odeSolver; card->get(odeSolver);
    odeSolver->f_rayleighStiffness.setValue(0.1);
    odeSolver->f_rayleighMass.setValue(0.1);

    CGLinearSolverGraph *cgLinearSolver; card->get(cgLinearSolver);
    cgLinearSolver->f_maxIter.setValue(15);
    cgLinearSolver->f_tolerance.setValue(1e-5);
    cgLinearSolver->f_smallDenominatorThreshold.setValue(1e-5);

    sofa::component::constraintset::LMConstraintSolver::SPtr constraintSolver = sofa::core::objectmodel::New<sofa::component::constraintset::LMConstraintSolver>();
    constraintSolver->constraintVel.setValue(true);
    constraintSolver->constraintPos.setValue(true);
    constraintSolver->numIterations.setValue(25);

    card->addObject(constraintSolver);

    MechanicalObjectRigid3::SPtr dofRigid = sofa::core::objectmodel::New<MechanicalObjectRigid3>(); dofRigid->setName("Rigid Object");
    dofRigid->setTranslation(position[0],position[1],position[2]);
    dofRigid->setRotation(rotation[0],rotation[1],rotation[2]);
    card->addObject(dofRigid);

    UniformMassRigid3::SPtr uniMassRigid = sofa::core::objectmodel::New<UniformMassRigid3>();
    uniMassRigid->setTotalMass(0.5);
    uniMassRigid->setFileMass(inertiaMatrix);
    card->addObject(uniMassRigid);

    //Node VISUAL
    Node::SPtr  RigidVisualNode = sofa::modeling::CreateVisualNodeRigid(card, dofRigid.get(), visualModel,colors[(colorIdx++)%7]);

    //Node COLLISION
    Node::SPtr  RigidCollisionNode = sofa::modeling::CreateCollisionNodeRigid(card, dofRigid.get(),collisionModel,modelTypes);

    return card;
}

std::pair<Node::SPtr , Node::SPtr  > create2Cards(Node::SPtr  parent, const Coord3& globalPosition, SReal distanceInBetween=SReal(0.1), SReal angle=SReal(15.0))
{
    //We assume the card has a 2 unit length, centered in (0,0,0)
    const SReal displacement=sin(convertDegreeToRadian(angle));
    const Coord3 separation=Coord3(displacement+distanceInBetween,0,0);

    //************************************
    //Left Rigid Card
    const Coord3 positionLeft=globalPosition + separation;
    const Coord3 rotationLeft(0,0,angle);
    Node::SPtr  leftCard = createCard(parent, positionLeft, rotationLeft);

    //************************************
    //Right Rigid Card
    const Coord3 positionRight=globalPosition - separation;
    const Coord3 rotationRight(0,0,-angle);
    Node::SPtr  rightCard = createCard(parent, positionRight, rotationRight);

    return std::make_pair(leftCard, rightCard);
}

Node::SPtr createHouseOfCards(Node::SPtr root,  unsigned int size, SReal distanceInBetween=SReal(0.1), SReal angle=SReal(15.0))
{

    //Elements of the scene
    //------------------------------------
    Node::SPtr  houseOfCards = root;

    //Space between two levels of the house of cards
    const SReal space=0.75;
    //Size of a card
    const SReal sizeCard=2;
    //overlap of the cards
    const SReal factor=0.95;
    const SReal distanceH=sizeCard*factor;
    const SReal distanceV=sizeCard*cos(convertDegreeToRadian(angle))+space;

    for (unsigned int i=0; i<size; ++i)
    {

        //Create the 2Cards
        for (unsigned int j=0; j<=i; ++j)
        {
            Coord3 position=Coord3((i+j)*(distanceH)*0.5,
                    (i-j)*(distanceV),
                    0);
            const std::pair<Node::SPtr , Node::SPtr > &cards=create2Cards(houseOfCards, position,distanceInBetween, angle);
            std::ostringstream out;
            out << "Card["<< i << "|" << j << "]";
            cards.first->setName("Left"+out.str());
            cards.second->setName("Right"+out.str());
        }

        //Create the support for the cards
        const Coord3 initPosition(0,sizeCard*0.5,0);
        const Coord3 supportRotation(0,0,90);
        for (unsigned int j=0; j<i; ++j)
        {
            Coord3 position((i+j)*distanceH*0.5,
                    (i-j)*distanceV-space*0.5+distanceInBetween*(j%2),
                    0);
            Node::SPtr supportCard=createCard(houseOfCards, position-initPosition, supportRotation);
            std::ostringstream out;
            out << "SupportCard["<< i << "|" << j << "]";
            supportCard->setName(out.str());
        }
    }

    return root;
}

int main(int argc, char** argv)
{
#ifndef WIN32
    // Reset local settings to make sure that floating-point values are interpreted correctly
    setlocale(LC_ALL,"C");
    setlocale(LC_NUMERIC,"C");
#endif

    glutInit(&argc,argv);

    sofa::gui::initMain();

    unsigned int sizeHouseOfCards=4;
    SReal angle=20.0;
    SReal distanceInBetween=0.1;
    SReal friction=0.8;
    SReal contactDistance=0.03;
    std::string gui = "";

    std::string gui_help = "choose the UI (";
    gui_help += sofa::gui::GUIManager::ListSupportedGUI('|');
    gui_help += ")";

    sofa::helper::parse("This is a SOFA application. Here are the command line arguments")
    .option(&sizeHouseOfCards,'l',"level","number of level of the house of cards")
    .option(&angle,'a',"angle","angle formed by two cards")
    .option(&distanceInBetween,'d',"distance","distance between two cards")
    .option(&friction,'f',"friction","friction coeff")
    .option(&contactDistance,'c',"contactDistance","contact distance")
    .option(&gui,'g',"gui",gui_help.c_str())
    (argc,argv);

    sofa::simulation::setSimulation(new sofa::simulation::tree::TreeSimulation());

    // The graph root node
    Node::SPtr root = sofa::modeling::CreateRootWithCollisionPipeline("distanceLMConstraint");
    root->setGravity( Coord3(0,-10,0) );
    root->setDt(0.001);

    sofa::component::collision::MinProximityIntersection *intersection;
    root->get(intersection, sofa::core::objectmodel::BaseContext::SearchDown);
    intersection->alarmDistance.setValue(contactDistance);
    intersection->contactDistance.setValue(contactDistance*0.5);

    //************************************
    //Floor
    Node::SPtr  torusFixed = sofa::modeling::CreateObstacle(root.get(),"mesh/floor.obj", "mesh/floor.obj", "gray");

    //Add the objects
    createHouseOfCards(root.get(),sizeHouseOfCards,distanceInBetween, angle);

    const SReal contactFriction=sqrt(friction);
    sofa::helper::vector< sofa::core::CollisionModel* > listCollisionModels;
    root->getTreeObjects<sofa::core::CollisionModel>(&listCollisionModels);
    for (unsigned int i=0; i<listCollisionModels.size(); ++i) listCollisionModels[i]->setContactFriction(contactFriction);
    root->setAnimate(false);

    //=======================================
    // Export the scene to file
    const std::string fileName="HouseOfCards.xml";
    sofa::simulation::getSimulation()->exportXML(root.get(),fileName.c_str());

    //=======================================
    // Destroy created scene: step needed, as I can't get rid of the locales (the mass can't init correctly as 0.1 is not considered as a floating point).
    sofa::simulation::DeleteVisitor deleteScene(sofa::core::ExecParams::defaultInstance() );
    root->execute(deleteScene);
    root.reset();

    //=======================================
    // Create the GUI
    if (int err=sofa::gui::GUIManager::Init(argv[0],gui.c_str()))
        return err;

    if (int err=sofa::gui::GUIManager::createGUI(NULL))
        return err;

    sofa::gui::GUIManager::SetDimension(800,600);
    //=======================================
    // Load the Scene

    sofa::simulation::Node::SPtr groot = sofa::core::objectmodel::SPtr_dynamic_cast<sofa::simulation::Node>( sofa::simulation::getSimulation()->load(fileName.c_str()));
    if (groot==NULL)
    {
        groot = sofa::simulation::getSimulation()->createNewGraph("");
    }

    sofa::simulation::getSimulation()->init(groot.get());
    sofa::gui::GUIManager::SetScene(groot,fileName.c_str());

    //=======================================
    // Run the main loop
    if (int err=sofa::gui::GUIManager::MainLoop(groot,fileName.c_str()))
        return err;
    Node* ggroot =  sofa::gui::GUIManager::CurrentSimulation();

    if (ggroot!=NULL) sofa::simulation::getSimulation()->unload(groot);
    return 0;
}
<commit_msg>r11131/sofa : push bc3ccbf..209ea8f<commit_after>/******************************************************************************
*       SOFA, Simulation Open-Framework Architecture, version 1.0 RC 1        *
*                (c) 2006-2011 INRIA, USTL, UJF, CNRS, MGH                    *
*                                                                             *
* This program is free software; you can redistribute it and/or modify it     *
* under the terms of the GNU General Public License as published by the Free  *
* Software Foundation; either version 2 of the License, or (at your option)   *
* any later version.                                                          *
*                                                                             *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    *
* more details.                                                               *
*                                                                             *
* You should have received a copy of the GNU General Public License along     *
* with this program; if not, write to the Free Software Foundation, Inc., 51  *
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.                   *
*******************************************************************************
*                            SOFA :: Applications                             *
*                                                                             *
* Authors: The SOFA Team and external contributors (see Authors.txt)          *
*                                                                             *
* Contact information: contact@sofa-framework.org                             *
******************************************************************************/

#include <plugins/SceneCreator/SceneCreator.h>

#include <sofa/helper/ArgumentParser.h>
#include <sofa/core/ExecParams.h>
#include <sofa/simulation/tree/TreeSimulation.h>
#include <sofa/simulation/common/Node.h>
#include <sofa/simulation/common/DeleteVisitor.h>

#include <sofa/gui/GUIManager.h>
#include <sofa/gui/Main.h>
#include <sofa/helper/system/FileRepository.h>

#include <sofa/component/collision/MinProximityIntersection.h>
#include <sofa/component/constraintset/LMConstraintSolver.h>
#include <sofa/component/odesolver/EulerImplicitSolver.h>
#include <sofa/component/linearsolver/CGLinearSolver.h>
#include <sofa/core/CollisionModel.h>

//Using double by default, if you have SOFA_FLOAT in use in you sofa-default.cfg, then it will be FLOAT.
#include <sofa/component/typedef/Sofa_typedef.h>
// ---------------------------------------------------------------------
// ---
// ---------------------------------------------------------------------
using namespace sofa::simulation;
using namespace sofa::component::container;
using namespace sofa::component::topology;
typedef sofa::component::linearsolver::CGLinearSolver< sofa::component::linearsolver::GraphScatteredMatrix, sofa::component::linearsolver::GraphScatteredVector> CGLinearSolverGraph;

const std::string colors[7]= {"red","green","blue","cyan","magenta","yellow","white"};

SReal convertDegreeToRadian(const SReal& angle)
{
    const SReal pi=3.14159265;
    return angle*pi/180.0;
}

Node::SPtr createCard(Node::SPtr  parent, const Coord3& position, const Coord3& rotation)
{
    const std::string visualModel="mesh/card.obj";
    const std::string collisionModel="mesh/cardCollision.obj";
    const std::string inertiaMatrix="BehaviorModels/card.rigid";
    static int colorIdx=0;

    std::vector<std::string> modelTypes;
    modelTypes.push_back("Triangle");
    modelTypes.push_back("Line");
    modelTypes.push_back("Point");

    Node::SPtr  card = sofa::modeling::createEulerSolverNode(parent,"Rigid","Implicit");

    sofa::component::odesolver::EulerImplicitSolver *odeSolver; card->get(odeSolver);
    odeSolver->f_rayleighStiffness.setValue(0.1);
    odeSolver->f_rayleighMass.setValue(0.1);

    CGLinearSolverGraph *cgLinearSolver; card->get(cgLinearSolver);
    cgLinearSolver->f_maxIter.setValue(15);
    cgLinearSolver->f_tolerance.setValue(1e-5);
    cgLinearSolver->f_smallDenominatorThreshold.setValue(1e-5);

    sofa::component::constraintset::LMConstraintSolver::SPtr constraintSolver = sofa::core::objectmodel::New<sofa::component::constraintset::LMConstraintSolver>();
    constraintSolver->constraintVel.setValue(true);
    constraintSolver->constraintPos.setValue(true);
    constraintSolver->numIterations.setValue(25);

    card->addObject(constraintSolver);

    MechanicalObjectRigid3::SPtr dofRigid = sofa::core::objectmodel::New<MechanicalObjectRigid3>(); dofRigid->setName("Rigid Object");
    dofRigid->setTranslation(position[0],position[1],position[2]);
    dofRigid->setRotation(rotation[0],rotation[1],rotation[2]);
    card->addObject(dofRigid);

    UniformMassRigid3::SPtr uniMassRigid = sofa::core::objectmodel::New<UniformMassRigid3>();
    uniMassRigid->setTotalMass(0.5);
    uniMassRigid->setFileMass(inertiaMatrix);
    card->addObject(uniMassRigid);

    //Node VISUAL
    Node::SPtr  RigidVisualNode = sofa::modeling::createVisualNodeRigid(card, dofRigid.get(), visualModel,colors[(colorIdx++)%7]);

    //Node COLLISION
    Node::SPtr  RigidCollisionNode = sofa::modeling::createCollisionNodeRigid(card, dofRigid.get(),collisionModel,modelTypes);

    return card;
}

std::pair<Node::SPtr , Node::SPtr  > create2Cards(Node::SPtr  parent, const Coord3& globalPosition, SReal distanceInBetween=SReal(0.1), SReal angle=SReal(15.0))
{
    //We assume the card has a 2 unit length, centered in (0,0,0)
    const SReal displacement=sin(convertDegreeToRadian(angle));
    const Coord3 separation=Coord3(displacement+distanceInBetween,0,0);

    //************************************
    //Left Rigid Card
    const Coord3 positionLeft=globalPosition + separation;
    const Coord3 rotationLeft(0,0,angle);
    Node::SPtr  leftCard = createCard(parent, positionLeft, rotationLeft);

    //************************************
    //Right Rigid Card
    const Coord3 positionRight=globalPosition - separation;
    const Coord3 rotationRight(0,0,-angle);
    Node::SPtr  rightCard = createCard(parent, positionRight, rotationRight);

    return std::make_pair(leftCard, rightCard);
}

Node::SPtr createHouseOfCards(Node::SPtr root,  unsigned int size, SReal distanceInBetween=SReal(0.1), SReal angle=SReal(15.0))
{

    //Elements of the scene
    //------------------------------------
    Node::SPtr  houseOfCards = root;

    //Space between two levels of the house of cards
    const SReal space=0.75;
    //Size of a card
    const SReal sizeCard=2;
    //overlap of the cards
    const SReal factor=0.95;
    const SReal distanceH=sizeCard*factor;
    const SReal distanceV=sizeCard*cos(convertDegreeToRadian(angle))+space;

    for (unsigned int i=0; i<size; ++i)
    {

        //Create the 2Cards
        for (unsigned int j=0; j<=i; ++j)
        {
            Coord3 position=Coord3((i+j)*(distanceH)*0.5,
                    (i-j)*(distanceV),
                    0);
            const std::pair<Node::SPtr , Node::SPtr > &cards=create2Cards(houseOfCards, position,distanceInBetween, angle);
            std::ostringstream out;
            out << "Card["<< i << "|" << j << "]";
            cards.first->setName("Left"+out.str());
            cards.second->setName("Right"+out.str());
        }

        //Create the support for the cards
        const Coord3 initPosition(0,sizeCard*0.5,0);
        const Coord3 supportRotation(0,0,90);
        for (unsigned int j=0; j<i; ++j)
        {
            Coord3 position((i+j)*distanceH*0.5,
                    (i-j)*distanceV-space*0.5+distanceInBetween*(j%2),
                    0);
            Node::SPtr supportCard=createCard(houseOfCards, position-initPosition, supportRotation);
            std::ostringstream out;
            out << "SupportCard["<< i << "|" << j << "]";
            supportCard->setName(out.str());
        }
    }

    return root;
}

int main(int argc, char** argv)
{
#ifndef WIN32
    // Reset local settings to make sure that floating-point values are interpreted correctly
    setlocale(LC_ALL,"C");
    setlocale(LC_NUMERIC,"C");
#endif

    glutInit(&argc,argv);

    sofa::gui::initMain();

    unsigned int sizeHouseOfCards=4;
    SReal angle=20.0;
    SReal distanceInBetween=0.1;
    SReal friction=0.8;
    SReal contactDistance=0.03;
    std::string gui = "";

    std::string gui_help = "choose the UI (";
    gui_help += sofa::gui::GUIManager::ListSupportedGUI('|');
    gui_help += ")";

    sofa::helper::parse("This is a SOFA application. Here are the command line arguments")
    .option(&sizeHouseOfCards,'l',"level","number of level of the house of cards")
    .option(&angle,'a',"angle","angle formed by two cards")
    .option(&distanceInBetween,'d',"distance","distance between two cards")
    .option(&friction,'f',"friction","friction coeff")
    .option(&contactDistance,'c',"contactDistance","contact distance")
    .option(&gui,'g',"gui",gui_help.c_str())
    (argc,argv);

    sofa::simulation::setSimulation(new sofa::simulation::tree::TreeSimulation());

    // The graph root node
    Node::SPtr root = sofa::modeling::createRootWithCollisionPipeline("distanceLMConstraint");
    root->setGravity( Coord3(0,-10,0) );
    root->setDt(0.001);

    sofa::component::collision::MinProximityIntersection *intersection;
    root->get(intersection, sofa::core::objectmodel::BaseContext::SearchDown);
    intersection->alarmDistance.setValue(contactDistance);
    intersection->contactDistance.setValue(contactDistance*0.5);

    //************************************
    //Floor
    Node::SPtr  torusFixed = sofa::modeling::createObstacle(root.get(),"mesh/floor.obj", "mesh/floor.obj", "gray");

    //Add the objects
    createHouseOfCards(root.get(),sizeHouseOfCards,distanceInBetween, angle);

    const SReal contactFriction=sqrt(friction);
    sofa::helper::vector< sofa::core::CollisionModel* > listCollisionModels;
    root->getTreeObjects<sofa::core::CollisionModel>(&listCollisionModels);
    for (unsigned int i=0; i<listCollisionModels.size(); ++i) listCollisionModels[i]->setContactFriction(contactFriction);
    root->setAnimate(false);

    //=======================================
    // Export the scene to file
    const std::string fileName="HouseOfCards.xml";
    sofa::simulation::getSimulation()->exportXML(root.get(),fileName.c_str());

    //=======================================
    // Destroy created scene: step needed, as I can't get rid of the locales (the mass can't init correctly as 0.1 is not considered as a floating point).
    sofa::simulation::DeleteVisitor deleteScene(sofa::core::ExecParams::defaultInstance() );
    root->execute(deleteScene);
    root.reset();

    //=======================================
    // Create the GUI
    if (int err=sofa::gui::GUIManager::Init(argv[0],gui.c_str()))
        return err;

    if (int err=sofa::gui::GUIManager::createGUI(NULL))
        return err;

    sofa::gui::GUIManager::SetDimension(800,600);
    //=======================================
    // Load the Scene

    sofa::simulation::Node::SPtr groot = sofa::core::objectmodel::SPtr_dynamic_cast<sofa::simulation::Node>( sofa::simulation::getSimulation()->load(fileName.c_str()));
    if (groot==NULL)
    {
        groot = sofa::simulation::getSimulation()->createNewGraph("");
    }

    sofa::simulation::getSimulation()->init(groot.get());
    sofa::gui::GUIManager::SetScene(groot,fileName.c_str());

    //=======================================
    // Run the main loop
    if (int err=sofa::gui::GUIManager::MainLoop(groot,fileName.c_str()))
        return err;
    Node* ggroot =  sofa::gui::GUIManager::CurrentSimulation();

    if (ggroot!=NULL) sofa::simulation::getSimulation()->unload(groot);
    return 0;
}
<|endoftext|>


<commit_before>// #include <possumwood_sdk/node_implementation.h>

// #include "possumwood_sdk/datatypes/enum.h"

// #include "datatypes/meshes.h"

// #include <CGAL/Polygon_mesh_processing/compute_normal.h>

// namespace {

// using possumwood::Meshes;
// using possumwood::CGALPolyhedron;

// dependency_graph::InAttr<possumwood::Enum> a_mode;
// dependency_graph::InAttr<Meshes> a_inMeshes;
// dependency_graph::OutAttr<Meshes> a_outMesh;

// // typename CGAL::Kernel_traits<
// //   typename boost::property_traits<
// //     typename boost::property_map<Mesh, CGAL::vertex_point_t>::type>::value_type>::Kernel Kernel;

// namespace {
// 	struct FakeKernel {
// 		typedef float FT;

// 		typedef possumwood::CGALKernel::Point_3 Point_3;

// 		struct Vector_3 {
// 			Vector_3(float x, float y, float z) : data{{x,y,z}} {
// 			}

// 			Vector_3(const std::array<float, 3>& arr) : data(arr) {
// 			}

// 			Vector_3(CGAL::Null_vector) : data{{0,0,0}} {
// 			}

// 			float& operator[](std::size_t index) {
// 				return data[index];
// 			}

// 			const float& operator[](std::size_t index) const {
// 				return data[index];
// 			}

// 			operator const std::array<float, 3>& () const {
// 				return data;
// 			}

// 			std::array<float, 3> data;
// 		};

// 		struct Equal_3 {
// 			bool operator()(const Vector_3& p, CGAL::Null_vector) {
// 				return p[0] == 0 && p[1] == 0 && p[2] == 0;
// 			}
// 		};

// 		auto construct_vector_3_object() const {
// 			struct Result {
// 				Vector_3 operator() (CGAL::Null_vector) {
// 					return Vector_3(CGAL::Null_vector());
// 				}

// 				Vector_3 operator() (const Vector_3& val) {
// 					return val;
// 				}

// 				Vector_3 operator() (const Point_3& p1, const Point_3& p2) {
// 					return Vector_3(p1[0]-p2[0], p1[1]-p2[1], p1[2]-p2[2]);
// 				}
// 			};

// 			return Result();
// 		}

// 		auto construct_sum_of_vectors_3_object() const {
// 			return [](const Vector_3& p1, const Vector_3& p2) {
// 				return std::array<float, 3>{{p1[0]+p2[0], p1[1]+p2[1], p1[2]+p2[2]}};
// 			};
// 		}

// 		auto construct_divided_vector_3_object() const {
// 			return [](const Vector_3& p1, float v) {
// 				return std::array<float, 3>{{p1[0]/v, p1[1]/v, p1[2]/v}};
// 			};
// 		}

// 		auto construct_scaled_vector_3_object() const {
// 			return [](const Vector_3& p1, float s) {
// 				return std::array<float, 3>{{p1[0]*s, p1[1]*s, p1[2]*s}};
// 			};
// 		}

// 		auto compute_squared_length_3_object() const {
// 			return [](const Vector_3& v1) {
// 				return v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2];
// 			};
// 		}

// 		auto construct_cross_product_vector_3_object() const {
// 			return [](const Vector_3& p1, const Vector_3& p2) {
// 				return std::array<float, 3>{{
// 					p1[1]*p2[2] - p1[2]*p2[1],
// 					p1[2]*p2[0] - p1[0]*p2[2],
// 					p1[0]*p2[1] - p1[1]*p2[0]
// 				}};
// 			};
// 		}
// 	};
// }

// dependency_graph::State compute(dependency_graph::Values& data) {
// 	const possumwood::Enum mode = data.get(a_mode);
// 	const Meshes& inMeshes = data.get(a_inMeshes);

// 	Meshes result;
// 	for(auto& inMesh : inMeshes) {
// 		std::unique_ptr<CGALPolyhedron> mesh(new CGALPolyhedron(inMesh.mesh()));

// 		// remove any existing normals
// 		{
// 			auto vertNormals = mesh->property_map<CGALPolyhedron::Vertex_index, std::array<float, 3>>("vec3:normals");
// 			if(vertNormals.second)
// 				mesh->remove_property_map(vertNormals.first);

// 			auto faceNormals = mesh->property_map<CGALPolyhedron::Face_index, std::array<float, 3>>("vec3:normals");
// 			if(faceNormals.second)
// 				mesh->remove_property_map(faceNormals.first);
// 		}

// 		// request for vertex normals
// 		if(mode.value() == "Per-vertex normals") {
// 			auto vertNormals = mesh->add_property_map<CGALPolyhedron::Vertex_index, std::array<float, 3>>("vec3:normals");
// 			CGAL::Polygon_mesh_processing::compute_vertex_normals(*mesh, vertNormals.first,
// 				CGAL::Polygon_mesh_processing::parameters::geom_traits(FakeKernel()));
// 		}

// 		// request for half-edge (polygon-vertex) normals
// 		else if(mode.value() == "Per-face normals") {
// 			auto faceNormals = mesh->add_property_map<CGALPolyhedron::Face_index, std::array<float, 3>>("vec3:normals");
// 			CGAL::Polygon_mesh_processing::compute_face_normals(*mesh, faceNormals.first,
// 				CGAL::Polygon_mesh_processing::parameters::geom_traits(FakeKernel()));
// 		}

// 		result.addMesh(inMesh.name(), std::move(mesh));
// 	}

// 	data.set(a_outMesh, result);

// 	return dependency_graph::State();
// }

// void init(possumwood::Metadata& meta) {
// 	meta.addAttribute(a_mode, "mode",
// 	                  possumwood::Enum({"Per-face normals", "Per-vertex normals"}));
// 	meta.addAttribute(a_inMeshes, "input");
// 	meta.addAttribute(a_outMesh, "output");

// 	meta.addInfluence(a_mode, a_outMesh);
// 	meta.addInfluence(a_inMeshes, a_outMesh);

// 	meta.setCompute(compute);
// }

// possumwood::NodeImplementation s_impl("cgal/normals_generator", init);
// }
<commit_msg>Changed the normals generator to work correctly with Properties<commit_after>#include <possumwood_sdk/node_implementation.h>

#include "possumwood_sdk/datatypes/enum.h"

#include "datatypes/meshes.h"

#include <CGAL/Polygon_mesh_processing/compute_normal.h>

namespace {

using possumwood::Meshes;
using possumwood::CGALPolyhedron;

dependency_graph::InAttr<possumwood::Enum> a_mode;
dependency_graph::InAttr<Meshes> a_inMeshes;
dependency_graph::OutAttr<Meshes> a_outMesh;

namespace {
	struct FakeKernel {
		typedef float FT;

		typedef possumwood::CGALKernel::Point_3 Point_3;

		struct Vector_3 {
			Vector_3(float x, float y, float z) : data{{x,y,z}} {
			}

			Vector_3(const std::array<float, 3>& arr) : data(arr) {
			}

			Vector_3(CGAL::Null_vector) : data{{0,0,0}} {
			}

			float& operator[](std::size_t index) {
				return data[index];
			}

			const float& operator[](std::size_t index) const {
				return data[index];
			}

			operator const std::array<float, 3>& () const {
				return data;
			}

			std::array<float, 3> data;
		};

		struct Equal_3 {
			bool operator()(const Vector_3& p, CGAL::Null_vector) {
				return p[0] == 0 && p[1] == 0 && p[2] == 0;
			}
		};

		auto construct_vector_3_object() const {
			struct Result {
				Vector_3 operator() (CGAL::Null_vector) {
					return Vector_3(CGAL::Null_vector());
				}

				Vector_3 operator() (const Vector_3& val) {
					return val;
				}

				Vector_3 operator() (const Point_3& p1, const Point_3& p2) {
					return Vector_3(p1[0]-p2[0], p1[1]-p2[1], p1[2]-p2[2]);
				}
			};

			return Result();
		}

		auto construct_sum_of_vectors_3_object() const {
			return [](const Vector_3& p1, const Vector_3& p2) {
				return std::array<float, 3>{{p1[0]+p2[0], p1[1]+p2[1], p1[2]+p2[2]}};
			};
		}

		auto construct_divided_vector_3_object() const {
			return [](const Vector_3& p1, float v) {
				return std::array<float, 3>{{p1[0]/v, p1[1]/v, p1[2]/v}};
			};
		}

		auto construct_scaled_vector_3_object() const {
			return [](const Vector_3& p1, float s) {
				return std::array<float, 3>{{p1[0]*s, p1[1]*s, p1[2]*s}};
			};
		}

		auto compute_squared_length_3_object() const {
			return [](const Vector_3& v1) {
				return v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2];
			};
		}

		auto construct_cross_product_vector_3_object() const {
			return [](const Vector_3& p1, const Vector_3& p2) {
				return std::array<float, 3>{{
					p1[1]*p2[2] - p1[2]*p2[1],
					p1[2]*p2[0] - p1[0]*p2[2],
					p1[0]*p2[1] - p1[1]*p2[0]
				}};
			};
		}
	};

	template<typename PROPERTY, typename ITERATOR>
	void put(PROPERTY& prop, const ITERATOR& target, const FakeKernel::Vector_3& norm) {
		prop.set(target->property_key(), norm);
	}
}

dependency_graph::State compute(dependency_graph::Values& data) {
	const possumwood::Enum mode = data.get(a_mode);

	Meshes result = data.get(a_inMeshes);
	for(auto& mesh : result) {
		// request for vertex normals
		if(mode.value() == "Per-vertex normals") {
			// remove face normals, if they exist
			if(mesh.faceProperties().hasProperty("vec3:normals"))
				mesh.faceProperties().removeProperty("vec3:normals");

			auto& normals = mesh.vertexProperties().addProperty("vec3:normals", std::array<float, 3>{{0,0,0}});

			CGAL::Polygon_mesh_processing::compute_vertex_normals(mesh.polyhedron(), normals,
				CGAL::Polygon_mesh_processing::parameters::geom_traits(FakeKernel()));
		}

		// request for face normals
		else if(mode.value() == "Per-face normals") {
			// remove vertex normals, if they exist
			if(mesh.vertexProperties().hasProperty("vec3:normals"))
				mesh.vertexProperties().removeProperty("vec3:normals");

			auto& normals = mesh.faceProperties().addProperty("vec3:normals", std::array<float, 3>{{0,0,0}});

			CGAL::Polygon_mesh_processing::compute_face_normals(mesh.polyhedron(), normals,
				CGAL::Polygon_mesh_processing::parameters::geom_traits(FakeKernel()));
		}
	}

	data.set(a_outMesh, result);

	return dependency_graph::State();
}

void init(possumwood::Metadata& meta) {
	meta.addAttribute(a_mode, "mode",
	                  possumwood::Enum({"Per-face normals", "Per-vertex normals"}));
	meta.addAttribute(a_inMeshes, "input");
	meta.addAttribute(a_outMesh, "output");

	meta.addInfluence(a_mode, a_outMesh);
	meta.addInfluence(a_inMeshes, a_outMesh);

	meta.setCompute(compute);
}

possumwood::NodeImplementation s_impl("cgal/normals_generator", init);
}
<|endoftext|>


<commit_before>/*
Copyright libCellML Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "gtest/gtest.h"

#include <libcellml>

TEST(ModelFlattening, importedUnits)
{
}
<commit_msg>Add basic tests for flattening a model with a units import and a component import.<commit_after>/*
Copyright libCellML Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "gtest/gtest.h"

#include <libcellml>

#include "test_utils.h"

TEST(ModelFlattening, importedUnits)
{
    const std::string unitsDefiniitonModel =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
        "<model xmlns=\"http://www.cellml.org/cellml/2.0#\" xmlns:cellml=\"http://www.cellml.org/cellml/2.0#\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" name=\"units\">\n"
        "  <units name=\"ms\">\n"
        "    <unit units=\"second\" prefix=\"milli\"/>\n"
        "  </units>\n"
        "</model>\n";

    const std::string modelImportingUnits =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
        "<model name=\"importer_of_units\" xmlns=\"http://www.cellml.org/cellml/2.0#\">\n"
        "  <import xlink:href=\"unitsdefinitions.xml\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"
        "    <units units_ref=\"ms\" name=\"my_ms\"/>\n"
        "  </import>\n"
        "</model>\n";

    const std::string e =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
        "<model xmlns=\"http://www.cellml.org/cellml/2.0#\" name=\"importer_of_units\">\n"
        "  <units name=\"my_ms\">\n"
        "    <unit prefix=\"milli\" units=\"second\"/>\n"
        "  </units>\n"
        "</model>\n";

    auto parser = libcellml::Parser::create();

    auto modelWithUnitsImports = parser->parseModel(modelImportingUnits);
    auto modelUnitsDefinitions = parser->parseModel(unitsDefiniitonModel);

    EXPECT_TRUE(modelWithUnitsImports->hasUnresolvedImports());

    auto units = modelWithUnitsImports->units(0);
    auto importSource = units->importSource();
    importSource->setModel(modelUnitsDefinitions);

    EXPECT_FALSE(modelWithUnitsImports->hasUnresolvedImports());

    modelWithUnitsImports->flatten();

    auto printer = libcellml::Printer::create();

    auto a = printer->printModel(modelWithUnitsImports);
    EXPECT_EQ(e, a);
}

TEST(ModelFlattening, importedComponent)
{
    const std::string sineDefinitionModel =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?><model xmlns=\"http://www.cellml.org/cellml/2.0#\" xmlns:cellml=\"http://www.cellml.org/cellml/2.0#\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" name=\"sin\" id=\"sin\">\n"
        "  <component name=\"sin\" id=\"sin\">\n"
        "    <variable name=\"x\" units=\"dimensionless\" interface=\"public_and_private\"/>\n"
        "    <variable id=\"sin\" units=\"dimensionless\" name=\"sin\" interface=\"public_and_private\"/>\n"
        "    <math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n"
        "      <apply id=\"actual_sin\"><eq/>\n"
        "        <ci>sin</ci>\n"
        "        <apply><sin/>\n"
        "          <ci>x</ci>\n"
        "        </apply>\n"
        "      </apply>\n"
        "    </math>\n"
        "  </component>\n"
        "</model>\n";

    const std::string modelImportingComponent =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
        "<model xmlns=\"http://www.cellml.org/cellml/2.0#\" xmlns:cellml=\"http://www.cellml.org/cellml/2.0#\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" name=\"sin_approximations_import\" id=\"sin_approximations_import\">\n"
        "  <import xlink:href=\"sin.xml\">\n"
        "    <component name=\"actual_sin\" component_ref=\"sin\"/>\n"
        "  </import>\n"
        "  <component name=\"main\" id=\"main\"/>\n"
        "  <encapsulation>\n"
        "    <component_ref component=\"main\">\n"
        "      <component_ref component=\"actual_sin\"/>\n"
        "    </component_ref>\n"
        "  </encapsulation>\n"
        "</model>\n";

    const std::string e =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
        "<model xmlns=\"http://www.cellml.org/cellml/2.0#\" name=\"sin_approximations_import\" id=\"sin_approximations_import\">\n"
        "  <component name=\"main\" id=\"main\"/>\n"
        "  <component name=\"actual_sin\" id=\"sin\">\n"
        "    <variable name=\"x\" units=\"dimensionless\" interface=\"public_and_private\"/>\n"
        "    <variable name=\"sin\" units=\"dimensionless\" interface=\"public_and_private\" id=\"sin\"/>\n"
        "    <math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n"
        "      <apply id=\"actual_sin\"><eq/>\n"
        "        <ci>sin</ci>\n"
        "        <apply><sin/>\n"
        "          <ci>x</ci>\n"
        "        </apply>\n"
        "      </apply>\n"
        "    </math>\n"
        "  </component>\n"
        "  <encapsulation>\n"
        "    <component_ref component=\"main\">\n"
        "      <component_ref component=\"actual_sin\"/>\n"
        "    </component_ref>\n"
        "  </encapsulation>\n"
        "</model>\n";

    auto parser = libcellml::Parser::create();

    auto modelWithComponentImport = parser->parseModel(modelImportingComponent);
    auto modelSineDefinition = parser->parseModel(sineDefinitionModel);

    EXPECT_TRUE(modelWithComponentImport->hasUnresolvedImports());

    auto component = modelWithComponentImport->component(0)->component(0);
    auto importSource = component->importSource();
    importSource->setModel(modelSineDefinition);

    EXPECT_TRUE(component->isImport());
    EXPECT_FALSE(modelWithComponentImport->hasUnresolvedImports());

    modelWithComponentImport->flatten();

    auto printer = libcellml::Printer::create();

    auto a = printer->printModel(modelWithComponentImport);
    EXPECT_EQ(e, a);
}
<|endoftext|>